#!/usr/bin/env python3
"""Smoke-test the monster stat table and manual guide annotations."""
from __future__ import annotations

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

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


ROOT = Path(__file__).resolve().parents[1]


def wait_for_monster_stats(port: int, session_id: str, timeout: float = 8) -> dict:
    script = """
return {
  readyState: document.readyState,
  title: document.title,
  ready: window.HWANSE_MONSTER_STATS_READY || null,
  statRowCount: document.querySelectorAll('[data-monster-stat-row]').length,
  nameOnlyRowCount: document.querySelectorAll('[data-monster-name-only-row]').length,
  guideMatchedCount: document.querySelectorAll('[data-guide-status="matched"]').length,
  guideMismatchCount: document.querySelectorAll('[data-guide-status="mismatch"]').length,
  portraitLinkCount: document.querySelectorAll('[data-monster-portrait]').length,
	  portraitUnboundCount: document.querySelectorAll('[data-portrait-unbound="true"]').length,
	  compactCandidateCount: document.querySelectorAll('[data-candidate-compact="true"]').length,
	  openCandidateDetailsCount: [...document.querySelectorAll('.candidate-details')].filter((item) => item.open).length,
	  summary: document.getElementById('summaryLine')?.textContent || '',
  text: document.body.textContent || '',
  firstRowText: document.querySelector('[data-monster-stat-row]')?.textContent || '',
  nameOnlyText: document.querySelector('[data-monster-name-only-row]')?.textContent || '',
  bootError: window.HWANSE_BOOT_ERROR || null,
};
"""
    deadline = time.monotonic() + timeout
    last_state: dict | None = None
    while time.monotonic() < deadline:
        last_state = execute_js(port, session_id, script)
        ready = last_state.get("ready") or {}
        if ready.get("rowCount") == 80 and last_state.get("statRowCount") == 80:
            return last_state
        time.sleep(0.1)
    raise WebDriverError(f"monster stats did not become ready: {last_state!r}")


def wait_for_portrait_tooltip(port: int, session_id: str, timeout: float = 8) -> dict:
    trigger_script = """
const link = document.querySelector('[data-monster-portrait]');
if (!link) return { triggered: false };
const rect = link.getBoundingClientRect();
link.dispatchEvent(new MouseEvent('mouseover', {
  bubbles: true,
  clientX: rect.left + 4,
  clientY: rect.top + 4,
  relatedTarget: null
}));
return {
  triggered: true,
  assetKey: link.dataset.assetKey || '',
};
"""
    triggered = execute_js(port, session_id, trigger_script)
    if not triggered.get("triggered"):
        raise WebDriverError(f"no portrait link to hover: {triggered!r}")

    probe_script = """
return {
  visible: document.getElementById('monsterPortraitTooltip')?.dataset.visible || '',
  title: document.getElementById('monsterPortraitTitle')?.textContent || '',
  meta: document.getElementById('monsterPortraitMeta')?.textContent || '',
  canvasWidth: document.getElementById('monsterPortraitCanvas')?.width || 0,
  canvasHeight: document.getElementById('monsterPortraitCanvas')?.height || 0,
};
"""
    deadline = time.monotonic() + timeout
    last_state: dict | None = None
    while time.monotonic() < deadline:
        last_state = execute_js(port, session_id, probe_script)
        if (
            last_state.get("visible") == "true"
            and last_state.get("canvasWidth", 0) > 1
            and last_state.get("canvasHeight", 0) > 1
        ):
            return {**triggered, **last_state}
        time.sleep(0.1)
    raise WebDriverError(f"portrait tooltip did not render: {last_state!r}")


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

    driver = shutil.which("WebKitWebDriver")
    if not driver:
        raise WebDriverError("WebKitWebDriver is not installed")

    driver_port = free_port()
    proc = subprocess.Popen([driver, f"--port={driver_port}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    session_id = ""
    try:
        wait_for_driver(driver_port, proc)
        response = request_json(
            driver_port,
            "POST",
            "/session",
            {"capabilities": {"alwaysMatch": {"browserName": "MiniBrowser"}}},
            timeout=30,
        )
        session_id = response["value"]["sessionId"]
        request_json(driver_port, "POST", f"/session/{session_id}/window/rect", {"width": 1360, "height": 900}, timeout=10)
        request_json(
            driver_port,
            "POST",
            f"/session/{session_id}/url",
            {"url": urljoin(args.base.rstrip("/") + "/", "web/monster_stats.html")},
            timeout=30,
        )
        state = wait_for_monster_stats(driver_port, session_id)
        ready = state["ready"]
        if ready.get("guideMatchedRowCount") != 80:
            raise WebDriverError(f"guide coverage mismatch: {ready!r}")
        if ready.get("monsterStaticDataReviewImplemented") is not True or ready.get("candidateFieldProfileImplemented") is not True:
            raise WebDriverError(f"monster static review marker missing: {ready!r}")
        if ready.get("monsterPortraitTooltipImplemented") is not True:
            raise WebDriverError(f"monster portrait tooltip marker missing: {ready!r}")
        if ready.get("monsterPortraitMappedRowCount", 0) < 50 or state.get("portraitLinkCount", 0) < 50:
            raise WebDriverError(f"monster portrait coverage too low: {state!r}")
        if ready.get("candidateFrontNumericFieldCountPerRow") != 9 or ready.get("candidateRateByteFieldCountPerRow") != 18:
            raise WebDriverError(f"candidate field profile mismatch: {ready!r}")
        if ready.get("candidateFieldCompactSummaryImplemented") is not True:
            raise WebDriverError(f"candidate compact summary marker missing: {ready!r}")
        if ready.get("candidateGlobalConstantFieldCount") != 3:
            raise WebDriverError(f"candidate global constants changed unexpectedly: {ready!r}")
        if state.get("compactCandidateCount") != 80 or state.get("openCandidateDetailsCount") != 0:
            raise WebDriverError(f"candidate fields are not compact by default: {state!r}")
        if "EXE 후보 필드" not in state.get("text", "") or "candidate +0x04..+0x27" not in state.get("summary", ""):
            raise WebDriverError(f"candidate field UI missing: {state!r}")
        if "EXE 후보 필드 압축" not in state.get("text", ""):
            raise WebDriverError(f"candidate compression note missing: {state!r}")
        if ready.get("nameOnlyCount") != 1 or state.get("nameOnlyRowCount") != 1:
            raise WebDriverError(f"name-only row mismatch: {state!r}")
        if "폭호" not in state.get("nameOnlyText", ""):
            raise WebDriverError(f"name-only 폭호 row missing: {state!r}")
        if state.get("guideMismatchCount", 0) < 1:
            raise WebDriverError(f"expected visible guide/EXE mismatches: {state!r}")
        portrait_state = wait_for_portrait_tooltip(driver_port, session_id)
        portrait_meta = portrait_state.get("meta", "")
        if "monster_review E0" not in portrait_meta or "40x64 @ 0,0" not in portrait_meta:
            raise WebDriverError(f"portrait tooltip did not report monster_review E0 rect: {portrait_state!r}")
        print(state)
        print(portrait_state)
        return 0
    finally:
        if session_id:
            try:
                request_json(driver_port, "DELETE", f"/session/{session_id}", timeout=5)
            except Exception:
                pass
        proc.terminate()
        try:
            proc.wait(timeout=3)
        except subprocess.TimeoutExpired:
            proc.kill()


if __name__ == "__main__":
    raise SystemExit(main())
