#!/usr/bin/env python3
"""
patch_tunnel_cutover.py — Cloudflare Tunnel cutover patcher

Save this file in the project root and run it from there:
    cd C:\\Users\\Administrator\\OneDrive\\fx_macro_intraday
    python patch_tunnel_cutover.py

What it does:
  1. live_signal_monitor_v3.py — Telegram dashboard links now use
     https://bennettsdashboard.com/dashboard.html (was raw IP over http).
     Existing tokens stay valid; only the address changes.
  2. dashboard_server.py — server becomes multi-threaded (one dead
     connection can no longer wedge everyone), gets a 75s per-connection
     timeout so half-open sockets from network switches clean themselves
     up, and binds to 127.0.0.1 only — the raw-IP route dies at the
     socket level, so only the Cloudflare tunnel can reach it.

Safety: backs up both files first (.bak_tunnel_<timestamp>), exact-match
verifies before and after, syntax-checks after. Re-runnable — detects
already-patched files and skips them.
"""

import shutil
import sys
import time
import py_compile
from pathlib import Path

ROOT = Path(__file__).resolve().parent

MONITOR = ROOT / "src" / "execution" / "live_signal_monitor_v3.py"
SERVER = ROOT / "src" / "execution" / "dashboard_server.py"

# {NL} is replaced with the file's own line-ending style at runtime
PATCHES = [
    {
        "path": MONITOR,
        "label": "Telegram dashboard link -> https://bennettsdashboard.com",
        "old": '"http://193.38.138.152:8765/dashboard.html")',
        "new": '"https://bennettsdashboard.com/dashboard.html")',
        "anchor": "DASHBOARD_BASE_URL",
    },
    {
        "path": SERVER,
        "label": "ThreadingHTTPServer + 127.0.0.1 bind + 75s socket timeout",
        "old": '    with http.server.HTTPServer(("", PORT), DashboardHandler) as httpd:',
        "new": '    DashboardHandler.timeout = 75{NL}'
               '    with http.server.ThreadingHTTPServer(("127.0.0.1", PORT), DashboardHandler) as httpd:',
        "anchor": "def run_server",
    },
]


def read_raw(path):
    # newline="" preserves the file's CRLF line endings exactly
    with open(path, "r", encoding="utf-8", newline="") as f:
        return f.read()


def write_raw(path, text):
    with open(path, "w", encoding="utf-8", newline="") as f:
        f.write(text)


def dump_region(text, anchor):
    """On a failed match, show the exact bytes near the expected spot."""
    i = text.find(anchor)
    if i == -1:
        print(f"    (anchor {anchor!r} not found either — file layout differs)")
        return
    start = max(0, i - 80)
    end = min(len(text), i + 320)
    print("    Exact bytes around anchor (repr):")
    print("    " + repr(text[start:end]))


def main():
    print("=" * 62)
    print("CLOUDFLARE TUNNEL CUTOVER PATCHER")
    print(f"  Root: {ROOT}")
    print("=" * 62)

    ts = time.strftime("%Y%m%d_%H%M%S")
    failures = 0
    patched_files = []

    for spec in PATCHES:
        path = spec["path"]
        print(f"\n[{path.name}] {spec['label']}")

        if not path.exists():
            print(f"  FAIL: file not found at {path}")
            print("        Make sure this script sits in the project root "
                  "(fx_macro_intraday).")
            failures += 1
            continue

        text = read_raw(path)
        nl = "\r\n" if "\r\n" in text else "\n"
        old = spec["old"]
        new = spec["new"].replace("{NL}", nl)

        if new in text and old not in text:
            print("  OK: already patched — skipping.")
            continue

        n = text.count(old)
        if n != 1:
            print(f"  FAIL: expected exactly 1 match, found {n}.")
            dump_region(text, spec["anchor"])
            failures += 1
            continue

        backup = path.with_name(path.name + f".bak_tunnel_{ts}")
        shutil.copy2(path, backup)
        print(f"  Backup: {backup.name}")

        write_raw(path, text.replace(old, new, 1))

        check = read_raw(path)
        if old in check or new not in check:
            print("  FAIL: post-write verification failed — restoring backup.")
            shutil.copy2(backup, path)
            failures += 1
            continue

        print("  Patched OK.")
        patched_files.append(path)

    # Syntax-check anything we touched
    for path in patched_files:
        try:
            py_compile.compile(str(path), doraise=True)
            print(f"\n[{path.name}] Syntax check OK.")
        except Exception as e:
            print(f"\n[{path.name}] SYNTAX CHECK FAILED: {e}")
            print("  Restore from the .bak_tunnel file and send me this output.")
            failures += 1

    print("\n" + "=" * 62)
    if failures:
        print(f"DONE WITH {failures} FAILURE(S) — send me the full output above.")
        sys.exit(1)
    print("ALL PATCHES APPLIED.")
    print("Next steps:")
    print("  1. Restart the dashboard server (Ctrl+C in its window, then):")
    print("       python src\\execution\\dashboard_server.py")
    print("  2. Restart the monitor the same way:")
    print("       python src\\execution\\live_signal_monitor_v3.py")
    print("  3. Phone test:  https://bennettsdashboard.com/dashboard.html?key=...")
    print("  4. Old IP test: http://193.38.138.152:8765/...  ->  should now FAIL.")
    print("=" * 62)


if __name__ == "__main__":
    main()
