#!/usr/bin/env python3
"""
patch_walls_page.py — wires the Call/Put Walls page into the dashboard.

Run AFTER the earlier patchers. Save to the project root:
    cd C:\\Users\\Administrator\\OneDrive\\fx_macro_intraday
    python patch_walls_page.py

Changes:
  dashboard_server.py
    1. Protects /walls.html + /walls_data.json behind the token check.
    2. Watches oi_data.xlsx — whenever a new copy lands (OneDrive sync),
       runs build_walls.py as a subprocess. 10-min backoff on failures.
       Logs to data\\logs\\walls_build.log.
    3. Calls the watcher from the existing 30-second update loop.
  dashboard.html
    4. Enables the Call/Put Walls entry in the Pages dropdown.

Safety: backups (.bak_walls_<timestamp>), exact-match verify, syntax
check, re-runnable.
"""

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

ROOT = Path(__file__).resolve().parent
SERVER = ROOT / "src" / "execution" / "dashboard_server.py"
DASH_HTML = ROOT / "dashboard.html"

WALLS_BLOCK = r'''# ── Call/Put Walls rebuild (walls.html page) ─────────────────────────────
WALLS_SCRIPT = BASE_PATH / "src" / "execution" / "build_walls.py"
WALLS_XLSX   = BASE_PATH / "oi_data.xlsx"
WALLS_JSON   = BASE_PATH / "walls_data.json"
WALLS_LOG    = BASE_PATH / "data" / "logs" / "walls_build.log"
_walls_lock = threading.Lock()
_walls_last_attempt = [0.0]


def _walls_stale():
    """True when oi_data.xlsx is newer than the last parsed build."""
    if not WALLS_SCRIPT.exists() or not WALLS_XLSX.exists():
        return False
    try:
        src_mtime = WALLS_XLSX.stat().st_mtime
    except OSError:
        return False
    if not WALLS_JSON.exists():
        return True
    try:
        with open(WALLS_JSON, encoding="utf-8") as f:
            done = json.load(f).get("source_mtime", 0)
    except Exception:
        return True
    return abs(src_mtime - done) > 1.0


def maybe_refresh_walls():
    """Rebuilds walls whenever a new oi_data.xlsx lands (OneDrive sync)."""
    if time.time() - _walls_last_attempt[0] < 600:
        return
    if not _walls_stale():
        return
    if not _walls_lock.acquire(blocking=False):
        return
    _walls_last_attempt[0] = time.time()

    def _run():
        try:
            print("  [walls] new oi_data.xlsx detected — rebuilding ...")
            with open(WALLS_LOG, "a", encoding="utf-8") as lf:
                lf.write(f"\n=== build {datetime.now()} ===\n")
                lf.flush()
                r = subprocess.run(
                    [sys.executable, str(WALLS_SCRIPT)],
                    cwd=str(BASE_PATH), stdout=lf,
                    stderr=subprocess.STDOUT, timeout=180,
                )
            print(f"  [walls] build finished (exit {r.returncode})")
        except Exception as e:
            print(f"  [walls] build failed: {e}")
        finally:
            _walls_lock.release()

    threading.Thread(target=_run, daemon=True).start()


'''

PATCHES = [
    {
        "path": SERVER,
        "label": "protect /walls.html + /walls_data.json",
        "old": '"/forecast_live.json")',
        "new": '"/forecast_live.json",\n'
               '                           "/walls.html", "/walls_data.json")',
        "marker": '"/walls_data.json")',
        "anchor": "_PROTECTED_PREFIXES",
        "compile": True,
    },
    {
        "path": SERVER,
        "label": "walls rebuild machinery (watches oi_data.xlsx)",
        "old": 'def main():\n    print("=" * 60)',
        "new": WALLS_BLOCK + 'def main():\n    print("=" * 60)',
        "marker": "def maybe_refresh_walls",
        "anchor": "def main",
        "compile": True,
    },
    {
        "path": SERVER,
        "label": "call maybe_refresh_walls() in the update loop",
        "old": '            maybe_refresh_live_quotes()\n'
               '            time.sleep(30)',
        "new": '            maybe_refresh_live_quotes()\n'
               '            maybe_refresh_walls()\n'
               '            time.sleep(30)',
        "marker": "            maybe_refresh_walls()",
        "anchor": "maybe_refresh_live_quotes",
        "compile": True,
    },
    {
        "path": DASH_HTML,
        "label": "enable Call/Put Walls in the Pages dropdown",
        "old": '      <option value="" disabled>▤ Call/Put Walls (soon)</option>',
        "new": '      <option value="./walls.html">▤ Call/Put Walls</option>',
        "marker": '<option value="./walls.html">',
        "anchor": "Call/Put Walls",
        "compile": False,
    },
]


def read_raw(path):
    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):
    i = text.find(anchor)
    if i == -1:
        print(f"    (anchor {anchor!r} not found either — file layout differs)")
        return
    print("    Exact bytes around anchor (repr):")
    print("    " + repr(text[max(0, i - 80):i + 320]))


def main():
    print("=" * 62)
    print("CALL/PUT WALLS PAGE PATCHER")
    print(f"  Root: {ROOT}")
    print("=" * 62)

    if not SERVER.exists():
        sys.exit(f"FAIL: {SERVER} not found — run from the project root.")
    if "def _session_now" not in read_raw(SERVER):
        sys.exit("FAIL: earlier forecast patches not applied — run "
                 "patch_forecast_page.py (and the others) first.")
    for p, where in [
        (ROOT / "src" / "execution" / "build_walls.py", "src\\execution\\"),
        (ROOT / "walls.html", "project root"),
    ]:
        if not p.exists():
            print(f"  NOTE: {p.name} not found yet — save it to {where} "
                  f"before restarting the server.")

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

    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}")
            failures += 1
            continue
        text = read_raw(path)
        nl = "\r\n" if "\r\n" in text else "\n"
        old = spec["old"].replace("\r\n", "\n").replace("\n", nl)
        new = spec["new"].replace("\r\n", "\n").replace("\n", nl)

        if spec["marker"] 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
        if path not in backed_up:
            backup = path.with_name(path.name + f".bak_walls_{ts}")
            shutil.copy2(path, backup)
            backed_up.add(path)
            print(f"  Backup: {backup.name}")
        write_raw(path, text.replace(old, new, 1))
        if spec["marker"] not in read_raw(path):
            print("  FAIL: post-write verification failed — check backup.")
            failures += 1
            continue
        print("  Patched OK.")
        if spec.get("compile") and path not in touched:
            touched.append(path)

    for path in touched:
        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_walls 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. python -m pip install openpyxl   (one time)")
    print("  2. Save build_walls.py -> src\\execution\\ ; walls.html -> root.")
    print("  3. Fill the template tabs and save it as  oi_data.xlsx  in the root.")
    print("  4. Test once:  python src\\execution\\build_walls.py")
    print("  5. Restart the dashboard server; hard-refresh the pages.")
    print("=" * 62)


if __name__ == "__main__":
    main()
