#!/usr/bin/env python3
"""Smoke-test the field-character speaker/identity review page."""
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"


STATE_SCRIPT = r"""
const marker = window.HWANSE_LAST_FIELD_CHARACTER_IDENTITY_REVIEW || null;
const canvases = Array.from(document.querySelectorAll('#rectGrid canvas'));
return {
  readyState: document.readyState,
  ready: window.HWANSE_FIELD_CHARACTER_IDENTITY_REVIEW_READY === true,
  marker,
  assetButtons: document.querySelectorAll('#assetList [data-asset]').length,
  speakerRows: document.querySelectorAll('#speakerRows tr').length,
  canvases: canvases.map((canvas) => ({
    width: canvas.width,
    height: canvas.height,
    visiblePixels: (() => {
      const data = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data;
      let count = 0;
      for (let index = 3; index < data.length; index += 4) if (data[index] > 0) count += 1;
      return count;
    })(),
  })),
  bodyText: document.body?.innerText || '',
  bootError: window.HWANSE_BOOT_ERROR || null,
};
"""


def wait_for_state(port: int, session_id: str, timeout: float = 12) -> dict:
    deadline = time.monotonic() + timeout
    last_state: dict | None = None
    while time.monotonic() < deadline:
        last_state = execute_js(port, session_id, STATE_SCRIPT, timeout=4)
        marker = last_state.get("marker") or {}
        if last_state.get("ready") and marker.get("ready") and marker.get("rectRenderReady") and last_state.get("canvases"):
            return last_state
        time.sleep(0.2)
    raise WebDriverError(f"field character identity review did not become ready: {last_state!r}")


def verify(base: str) -> None:
    driver = shutil.which("WebKitWebDriver")
    if not driver:
        raise WebDriverError("WebKitWebDriver is not installed")
    port = free_port()
    log_path = OUT / "field_character_identity_review_webkitdriver.log"
    with log_path.open("wb") as log:
        process = subprocess.Popen(
            [driver, "--host=127.0.0.1", f"--port={port}", "--replace-on-new-session"],
            stdout=log,
            stderr=subprocess.STDOUT,
        )
        session_id = ""
        try:
            wait_for_driver(port, process)
            response = request_json(
                port,
                "POST",
                "/session",
                {"capabilities": {"alwaysMatch": {"browserName": "MiniBrowser"}}},
                timeout=30,
            )
            session_id = str((response or {}).get("value", {}).get("sessionId") or "")
            if not session_id:
                raise WebDriverError(f"could not create WebKit session: {response!r}")
            request_json(
                port,
                "POST",
                f"/session/{session_id}/window/rect",
                {"x": 0, "y": 0, "width": 1360, "height": 900},
                timeout=8,
            )
            url = urljoin(base.rstrip("/") + "/", "web/field_character_identity_review.html?asset=cara_at1")
            request_json(port, "POST", f"/session/{session_id}/url", {"url": url}, timeout=30)
            state = wait_for_state(port, session_id)
            marker = state["marker"]
            if marker.get("asset") != "cara_at1" or marker.get("identityStatus") != "confirmed-cns-identity":
                raise WebDriverError(f"confirmed identity selection mismatch: {state!r}")
            if marker.get("fieldCnsCount") != 33 or marker.get("speakerLabelCount") != 122:
                raise WebDriverError(f"dataset summary mismatch: {state!r}")
            if not marker.get("continuationFirstLinesExcluded") or not marker.get("monsterCnsExcluded"):
                raise WebDriverError(f"evidence policy marker mismatch: {state!r}")
            if state.get("assetButtons", 0) < 30 or state.get("speakerRows", 0) < 100:
                raise WebDriverError(f"asset/speaker lists did not render: {state!r}")
            if not state.get("canvases") or any(row.get("visiblePixels", 0) <= 0 for row in state["canvases"]):
                raise WebDriverError(f"one or more CNS rect canvases are blank: {state!r}")
            if "아타호" not in state.get("bodyText", "") or "이름 미연결" not in state.get("bodyText", ""):
                raise WebDriverError(f"identity boundary labels are missing: {state!r}")
            execute_js(
                port,
                session_id,
                "document.querySelector('#assetList [data-asset=\"cara_07\"]')?.click(); return true;",
                timeout=4,
            )
            candidate_state = wait_for_state(port, session_id)
            candidate_marker = candidate_state.get("marker") or {}
            if candidate_marker.get("asset") != "cara_07" or candidate_marker.get("identityStatus") != "rect-identity-unbound":
                raise WebDriverError(f"candidate identity selection mismatch: {candidate_state!r}")
            if "주작권사" not in candidate_state.get("bodyText", "") or "dialogue-block-resource-candidate" not in candidate_state.get("bodyText", ""):
                raise WebDriverError(f"candidate evidence did not render: {candidate_state!r}")
            report = {
                "status": "ok",
                "url": url,
                "marker": marker,
                "assetButtons": state["assetButtons"],
                "speakerRows": state["speakerRows"],
                "canvasCount": len(state["canvases"]),
                "candidateAsset": candidate_marker.get("asset"),
            }
            (OUT / "field_character_identity_review_browser_smoke.json").write_text(
                json.dumps(report, ensure_ascii=False, indent=2) + "\n",
                encoding="utf-8",
            )
            print(json.dumps(report, ensure_ascii=False))
        finally:
            if session_id:
                try:
                    request_json(port, "DELETE", f"/session/{session_id}", None, timeout=5)
                except Exception:
                    pass
            process.terminate()
            try:
                process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                process.kill()


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--base", default="http://127.0.0.1:8013/")
    args = parser.parse_args()
    verify(args.base)


if __name__ == "__main__":
    main()
