#!/usr/bin/env python3
"""Smoke-test the visual map gallery through WebKit."""
from __future__ import annotations

import argparse
import json
import shutil
import subprocess
import time
from pathlib import Path
from urllib.parse import urljoin

from browser_smoke_helpers import (
    WebDriverError,
    execute_js,
    free_port,
    request_json,
    wait_for_driver,
)


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"


def state_script() -> str:
    return """
const render = window.HWANSE_LAST_MAP_GALLERY_RENDER || null;
return {
  readyState: document.readyState,
  href: location.href,
  render,
  summary: document.getElementById('mapVisualGridSummary')?.textContent || '',
  cardCount: document.querySelectorAll('[data-map-visual-card]').length,
  canvasCount: document.querySelectorAll('[data-map-visual-canvas]').length,
  firstCard: document.querySelector('[data-map-visual-card]')?.dataset.map || '',
  map0CardCount: document.querySelectorAll('[data-map-visual-card][data-map^="map0_"]').length,
  eventKindCount: document.querySelectorAll('[data-map-visual-card][data-kind="event"]').length,
  fieldKindCount: document.querySelectorAll('[data-map-visual-card][data-kind="field"]').length,
  firstCanvasWidth: document.querySelector('[data-map-visual-canvas]')?.width || 0,
  firstCanvasHeight: document.querySelector('[data-map-visual-canvas]')?.height || 0,
  searchValue: document.getElementById('mapSearch')?.value || '',
};
"""


def wait_for_review(port: int, session_id: str, timeout: float = 40, predicate=None) -> dict:
    deadline = time.monotonic() + timeout
    last_state = {}
    while time.monotonic() < deadline:
        state = execute_js(port, session_id, state_script(), timeout=3)
        last_state = state
        render = state.get("render") or {}
        if (
            state.get("readyState") == "complete"
            and render.get("browserMapVisualGridImplemented") is True
            and (predicate is None or predicate(state))
        ):
            return state
        time.sleep(0.25)
    raise WebDriverError(f"map gallery visual grid did not become ready: {last_state!r}")


def write_report(payload: dict) -> None:
    OUT.mkdir(parents=True, exist_ok=True)
    (OUT / "map_gallery_visual_browser_smoke.json").write_text(
        json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    lines = [
        "# Map Gallery Visual Browser Smoke",
        "",
        f"- status: `{payload.get('status')}`",
        f"- cards: `{payload['default'].get('cardCount')}`",
        f"- map0 cards: `{payload['default'].get('map0CardCount')}`",
        f"- first card: `{payload['default'].get('firstCard')}`",
        f"- search cards: `{payload['map0Search'].get('cardCount')}`",
        "",
    ]
    (OUT / "map_gallery_visual_browser_smoke.md").write_text("\n".join(lines), encoding="utf-8")


def verify_browser(base: str, keep_log: bool = False) -> None:
    driver_path = shutil.which("WebKitWebDriver")
    if not driver_path:
        raise WebDriverError("WebKitWebDriver is not installed; install webkit2gtk-driver and run under Xvfb")

    port = free_port()
    log_path = OUT / "map_gallery_visual_webkitdriver.log"
    log_path.parent.mkdir(parents=True, exist_ok=True)
    with log_path.open("wb") as log:
        proc = subprocess.Popen(
            [
                driver_path,
                "--host=127.0.0.1",
                f"--port={port}",
                "--replace-on-new-session",
            ],
            stdout=log,
            stderr=subprocess.STDOUT,
        )
        session_id = ""
        try:
            wait_for_driver(port, proc)
            session = request_json(
                port,
                "POST",
                "/session",
                {"capabilities": {"alwaysMatch": {"browserName": "MiniBrowser"}}},
                timeout=30,
            )
            session_id = str((session or {}).get("value", {}).get("sessionId") or "")
            if not session_id:
                raise WebDriverError(f"could not create WebKit session: {session!r}")
            request_json(
                port,
                "POST",
                f"/session/{session_id}/window/rect",
                {"x": 0, "y": 0, "width": 1440, "height": 1000},
                timeout=8,
            )

            url = urljoin(base.rstrip("/") + "/", "/web/map_gallery.html")
            request_json(port, "POST", f"/session/{session_id}/url", {"url": url}, timeout=30)
            default_state = wait_for_review(
                port,
                session_id,
                predicate=lambda state: (state.get("render") or {}).get("mapVisualGridDrawnCanvasCount") == 174,
            )
            render = default_state.get("render") or {}
            if (
                render.get("mapVisualGridTotalMapCount") != 174
                or render.get("mapVisualGridAcceptedRowCount") != 174
                or render.get("mapVisualGridVisibleCount") != 174
                or render.get("mapVisualGridEventMapCount") != 4
                or render.get("mapVisualGridIncludesMap0") is not True
                or render.get("mapVisualGridSource") != "out/maps.js"
                or render.get("browserMapVisualGridUsesCanvas") is not True
                or default_state.get("cardCount") != 174
                or default_state.get("canvasCount") != 174
                or default_state.get("map0CardCount") != 4
                or default_state.get("eventKindCount") != 4
                or default_state.get("fieldKindCount") != 170
                or default_state.get("firstCard") != "map0_01n"
                or default_state.get("firstCanvasWidth", 0) <= 0
                or default_state.get("firstCanvasHeight", 0) <= 0
            ):
                raise WebDriverError(f"default map gallery visual mismatch: {default_state!r}")

            execute_js(
                port,
                session_id,
                """
const input = document.getElementById('mapSearch');
input.value = 'map0_';
input.dispatchEvent(new Event('input'));
return true;
""",
                timeout=3,
            )
            map0_state = wait_for_review(
                port,
                session_id,
                predicate=lambda state: state.get("cardCount") == 4 and (state.get("render") or {}).get("mapVisualGridVisibleCount") == 4,
            )
            if (
                map0_state.get("map0CardCount") != 4
                or map0_state.get("eventKindCount") != 4
                or map0_state.get("fieldKindCount") != 0
                or map0_state.get("searchValue") != "map0_"
                or not str(map0_state.get("summary", "")).startswith("visible 4/174")
            ):
                raise WebDriverError(f"map0 search did not filter the visual grid: {map0_state!r}")

            payload = {"status": "passed", "default": default_state, "map0Search": map0_state}
            write_report(payload)
            print(
                "ok map_gallery_visual "
                f"cards={default_state.get('cardCount')} "
                f"map0={default_state.get('map0CardCount')} "
                f"drawn={render.get('mapVisualGridDrawnCanvasCount')}"
            )
        finally:
            if session_id:
                try:
                    request_json(port, "DELETE", f"/session/{session_id}", None, timeout=5)
                except Exception:
                    pass
            proc.terminate()
            try:
                proc.wait(timeout=5)
            except subprocess.TimeoutExpired:
                proc.kill()
            if not keep_log and log_path.exists():
                log_path.unlink()


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--base", default="http://127.0.0.1:8013")
    parser.add_argument("--keep-log", action="store_true")
    args = parser.parse_args()
    verify_browser(args.base, keep_log=args.keep_log)


if __name__ == "__main__":
    main()
