"""
FreeExile Real-Browser QA Automation Harness Utilities.
Provides server liveness management, Playwright browser bootstrap,
and in-page telemetry probe instrumentation.
"""

from __future__ import annotations
import json
import os
import socket
import subprocess
import sys
import time
from pathlib import Path
from typing import Any, Dict, Optional, Tuple

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
TELEMETRY_DIR = PROJECT_ROOT / "docs" / "qa" / "reports" / "telemetry"

INJECT_PROBE_SCRIPT = """
(() => {
  if (window.__qaProbeReady) return;
  window.__qaProbeReady = true;

  window.__qaTelemetry = {
    frameTimes: [],
    jankCount: 0,
    lastFrameTime: performance.now(),
    navCount: 1,
    active: true,
    inputDrops: 0,
    init() {
      window.addEventListener('beforeunload', () => { this.navCount++; });
      const record = (now) => {
        if (!this.active) return;
        const dt = now - this.lastFrameTime;
        this.lastFrameTime = now;
        if (dt > 0 && dt < 500) {
          this.frameTimes.push(Number(dt.toFixed(2)));
          if (dt > 33.33) this.jankCount++;
        }
        requestAnimationFrame(record);
      };
      requestAnimationFrame(record);
    },
    getFpsStats() {
      const times = this.frameTimes.slice();
      if (times.length === 0) return { avgFps: 60, minFps: 60, maxFps: 60, p95: 16.6, p99: 16.6, count: 0 };
      times.sort((a, b) => a - b);
      const avg = times.reduce((s, v) => s + v, 0) / times.length;
      const minDt = times[0];
      const maxDt = times[times.length - 1];
      const p95 = times[Math.floor(times.length * 0.95)] || maxDt;
      const p99 = times[Math.floor(times.length * 0.99)] || maxDt;
      return {
        sampleCount: times.length,
        avgFps: Number((1000 / avg).toFixed(1)),
        minFps: Number((1000 / maxDt).toFixed(1)),
        maxFps: Number((1000 / minDt).toFixed(1)),
        avgFrameTimeMs: Number(avg.toFixed(2)),
        p95FrameTimeMs: Number(p95.toFixed(2)),
        p99FrameTimeMs: Number(p99.toFixed(2)),
        jankCount: this.jankCount,
        jankRatio: Number(((this.jankCount / times.length) * 100).toFixed(2))
      };
    },
    getMemorySnapshot() {
      const mem = window.performance?.memory || {};
      const tmrMem = window.TileMapRenderer?.getMemoryUsage?.() || 0;
      return {
        usedHeapMB: Number(((mem.usedJSHeapSize || 0) / (1024 * 1024)).toFixed(2)),
        totalHeapMB: Number(((mem.totalJSHeapSize || 0) / (1024 * 1024)).toFixed(2)),
        tmrBytes: tmrMem,
        tmrMB: Number((tmrMem / (1024 * 1024)).toFixed(2)),
        domNodes: document.getElementsByTagName('*').length,
        canvasCount: document.getElementsByTagName('canvas').length,
        activeParticles: Array.isArray(window.particles) ? window.particles.length : 0,
        activeMonsters: Array.isArray(window.activeMonsters) ? window.activeMonsters.length : 0
      };
    }
  };
  window.__qaTelemetry.init();
})();
"""


def is_port_open(host: str = "127.0.0.1", port: int = 8088) -> bool:
    """Checks whether the specified TCP port is listening."""
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.settimeout(0.8)
        return s.connect_ex((host, port)) == 0


def ensure_server_running(port: int = 8088) -> Optional[subprocess.Popen]:
    """Ensures local web server is running on port 8088, launching if necessary."""
    if is_port_open("127.0.0.1", port):
        print(f"[QA Server] Server already active on port {port}.")
        return None
    print(f"[QA Server] Starting local web server on port {port}...")
    server_script = PROJECT_ROOT / "tools" / "serve_webapp.py"
    proc = subprocess.Popen(
        [sys.executable, str(server_script), "--port", str(port)],
        cwd=str(PROJECT_ROOT),
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    for _ in range(30):
        time.sleep(0.2)
        if is_port_open("127.0.0.1", port):
            print(f"[QA Server] Server successfully started (PID: {proc.pid}).")
            return proc
    raise RuntimeError(f"Failed to start local web server on port {port}.")


def launch_browser_instance(playwright_ctx, headless: bool = True):
    """Launches Chromium browser using Microsoft Edge or system Chromium."""
    try:
        return playwright_ctx.chromium.launch(channel="msedge", headless=headless)
    except Exception:
        return playwright_ctx.chromium.launch(headless=headless)


def ensure_telemetry_directory() -> Path:
    """Creates the telemetry report output directory if not present."""
    TELEMETRY_DIR.mkdir(parents=True, exist_ok=True)
    return TELEMETRY_DIR


def write_json_telemetry(filename: str, data: Dict[str, Any]) -> Path:
    """Writes telemetry dictionary into telemetry reports folder."""
    out_dir = ensure_telemetry_directory()
    target = out_dir / filename
    with open(target, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)
    print(f"[QA Telemetry] Written: {target}")
    return target
