#!/usr/bin/env python3
"""Smoke-test battle_background_review.html 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_BATTLE_BACKGROUND_REVIEW_RENDER || null;
return {
  readyState: document.readyState,
  href: location.href,
  render,
  statusText: document.getElementById('status')?.textContent || '',
  backgroundValue: document.getElementById('backgroundSelect')?.value || '',
  familyValue: document.getElementById('familySelect')?.value || '',
  renderModeValue: document.getElementById('renderModeSelect')?.value || '',
  scaleValue: document.getElementById('scaleSelect')?.value || '',
  selectedTitle: document.getElementById('selectedTitle')?.textContent || '',
  coverageText: document.getElementById('coverageRows')?.textContent || '',
  coverageRowCount: document.querySelectorAll('#coverageRows tr').length,
  galleryCardCount: document.querySelectorAll('#gallery .card').length,
  canvasCount: document.querySelectorAll('[data-battle-background-canvas]').length,
  selectedCanvasWidth: document.getElementById('selectedCanvas')?.width || 0,
  selectedCanvasHeight: document.getElementById('selectedCanvas')?.height || 0,
  backgroundOptions: [...document.querySelectorAll('#backgroundSelect option')].map((node) => node.value),
};
"""


def wait_for_review(port: int, session_id: str, timeout: float = 10, 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("browserBattleBackgroundReviewImplemented") is True
            and (predicate is None or predicate(state))
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"battle background review did not become ready: {last_state!r}")


def write_report(payload: dict) -> None:
    OUT.mkdir(parents=True, exist_ok=True)
    (OUT / "battle_background_review_browser_smoke.json").write_text(
        json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    lines = [
        "# Battle Background Review Browser Smoke",
        "",
        f"- status: `{payload.get('status')}`",
        f"- selected: `{payload['default'].get('selectedTitle')}`",
        f"- background count: `{(payload['default'].get('render') or {}).get('battleBackgroundCount')}`",
        f"- total tilemap CNS coverage: `{(payload['default'].get('render') or {}).get('totalTilemapCnsCoverageCount')}`",
        f"- family i visible count: `{(payload['familyI'].get('render') or {}).get('visibleBattleBackgroundCount')}`",
        "",
    ]
    (OUT / "battle_background_review_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 / "battle_background_review_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": 1280, "height": 900},
                timeout=8,
            )

            url = urljoin(base.rstrip("/") + "/", "/web/battle_background_review.html?background=btl_n1")
            request_json(port, "POST", f"/session/{session_id}/url", {"url": url}, timeout=30)
            default_state = wait_for_review(port, session_id)
            default_render = default_state.get("render") or {}
            if (
                default_render.get("battleBackgroundCount") != 26
                or default_render.get("selectedBattleBackground") != "btl_n1"
                or default_render.get("selectedBattleBackgroundLayer1Nonzero") != 88
                or default_render.get("renderMode") != "foreground"
                or default_render.get("battleBackgroundForegroundOverlaySource") != "layer0-same-index"
                or default_render.get("battleBackgroundLayer1DataVisualMode") is not False
                or default_render.get("fieldTilemapCoverageCount") != 174
                or default_render.get("totalTilemapCnsCoverageCount") != 200
                or default_render.get("expectedTilemapCnsCoverageCount") != 200
                or default_render.get("allTilemapCnsWebVisible") is not True
                or default_render.get("imageCnsStillSeparate") is not True
                or default_render.get("originalEncounterBindingProven") is not False
                or default_state.get("coverageRowCount") != 5
                or default_state.get("galleryCardCount") != 26
                or default_state.get("canvasCount", 0) < 27
                or default_state.get("selectedCanvasWidth") != 1184
                or default_state.get("selectedCanvasHeight") != 672
                or "btl_etc.cns" not in default_state.get("coverageText", "")
            ):
                raise WebDriverError(f"default battle background review mismatch: {default_state!r}")

            execute_js(
                port,
                session_id,
                """
const family = document.getElementById('familySelect');
family.value = 'i';
family.dispatchEvent(new Event('change'));
return true;
""",
                timeout=3,
            )
            family_state = wait_for_review(
                port,
                session_id,
                predicate=lambda state: (state.get("render") or {}).get("visibleBattleBackgroundCount") == 5,
            )
            family_render = family_state.get("render") or {}
            if (
                family_state.get("familyValue") != "i"
                or family_render.get("visibleBattleBackgroundCount") != 5
                or not str(family_render.get("selectedBattleBackground", "")).startswith("btl_i")
                or family_state.get("galleryCardCount") != 5
            ):
                raise WebDriverError(f"family filter did not narrow battle backgrounds: {family_state!r}")

            payload = {"status": "passed", "default": default_state, "familyI": family_state}
            write_report(payload)
            print(
                "ok battle_background_review "
                f"backgrounds={default_render.get('battleBackgroundCount')} "
                f"tilemapCoverage={default_render.get('totalTilemapCnsCoverageCount')} "
                f"familyI={family_render.get('visibleBattleBackgroundCount')}"
            )
        finally:
            if session_id:
                try:
                    request_json(port, "DELETE", f"/session/{session_id}", timeout=3)
                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()
