"""CLI command: penbot watch — run PenBot on a schedule and detect regressions."""

from __future__ import annotations

import asyncio
import json
import signal
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

from src.cli.machine_output import is_machine, emit_json, emit_jsonl
from src.utils.logging import get_logger

logger = get_logger(__name__)

SESSIONS_DIR = Path("sessions")


def _parse_interval(spec: str) -> int:
    """Parse interval like '6h', '30m', '1d' into seconds."""
    spec = spec.strip().lower()
    multipliers = {"s": 1, "m": 60, "h": 3600, "d": 86400}
    if spec[-1] in multipliers:
        return int(spec[:-1]) * multipliers[spec[-1]]
    return int(spec)


def _load_session(path: Path) -> Optional[dict]:
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return None


async def _run_single_test(config_path: str, quick: bool, machine: bool) -> Optional[str]:
    """Run a single penbot test and return the session ID."""
    from src.cli.test_runner import run_test
    import argparse

    args = argparse.Namespace(
        config=config_path,
        machine=machine,
        quick=quick,
        agents="",
        max_attacks=0,
        verbose=False,
    )

    try:
        await run_test(args)
    except SystemExit:
        pass

    # Find the latest session file
    if not SESSIONS_DIR.exists():
        return None
    files = sorted(SESSIONS_DIR.glob("*.json"), key=lambda f: f.stat().st_mtime, reverse=True)
    if files:
        data = _load_session(files[0])
        if data:
            return data.get("test_session_id", files[0].stem)
    return None


def _compare_sessions(session_a: dict, session_b: dict) -> dict:
    """Compare two test sessions to detect regressions.

    Returns a diff summary with new/resolved/persistent findings.
    """
    findings_a = {
        (f.get("category"), f.get("severity")): f for f in session_a.get("security_findings", [])
    }
    findings_b = {
        (f.get("category"), f.get("severity")): f for f in session_b.get("security_findings", [])
    }

    keys_a = set(findings_a.keys())
    keys_b = set(findings_b.keys())

    new_vulns = keys_b - keys_a
    resolved_vulns = keys_a - keys_b
    persistent_vulns = keys_a & keys_b

    score_a = session_a.get("vulnerability_score", 0)
    score_b = session_b.get("vulnerability_score", 0)

    return {
        "session_a": session_a.get("test_session_id", "unknown"),
        "session_b": session_b.get("test_session_id", "unknown"),
        "target_name": session_b.get("target_name", session_a.get("target_name", "Unknown")),
        "score_before": score_a,
        "score_after": score_b,
        "score_delta": round(score_b - score_a, 2),
        "regression": score_b > score_a,
        "new_vulnerabilities": [{"category": k[0], "severity": k[1]} for k in sorted(new_vulns)],
        "resolved_vulnerabilities": [
            {"category": k[0], "severity": k[1]} for k in sorted(resolved_vulns)
        ],
        "persistent_vulnerabilities": [
            {"category": k[0], "severity": k[1]} for k in sorted(persistent_vulns)
        ],
        "findings_before": len(findings_a),
        "findings_after": len(findings_b),
    }


def run_diff(args):
    """Entry point for ``penbot diff``."""
    machine = is_machine(args)
    session_a_id = args.session_a
    session_b_id = args.session_b

    # Load sessions
    path_a = SESSIONS_DIR / f"{session_a_id}.json"
    path_b = SESSIONS_DIR / f"{session_b_id}.json"

    if not path_a.exists():
        files = sorted(SESSIONS_DIR.glob("*.json"), key=lambda f: f.stat().st_mtime)
        try:
            idx = int(session_a_id) - 1
            path_a = files[idx] if 0 <= idx < len(files) else path_a
        except (ValueError, IndexError):
            pass

    if not path_b.exists():
        files = sorted(SESSIONS_DIR.glob("*.json"), key=lambda f: f.stat().st_mtime)
        try:
            idx = int(session_b_id) - 1
            path_b = files[idx] if 0 <= idx < len(files) else path_b
        except (ValueError, IndexError):
            pass

    if not path_a.exists() or not path_b.exists():
        msg = f"Session file not found: {path_a if not path_a.exists() else path_b}"
        if machine:
            emit_json({"error": msg})
        else:
            print(f"\n  Error: {msg}")
        sys.exit(1)

    data_a = _load_session(path_a)
    data_b = _load_session(path_b)

    if not data_a or not data_b:
        msg = "Failed to parse session files"
        if machine:
            emit_json({"error": msg})
        else:
            print(f"\n  Error: {msg}")
        sys.exit(1)

    diff = _compare_sessions(data_a, data_b)

    if machine:
        emit_json(diff)
        return

    # Human-readable output
    from rich.console import Console
    from rich.panel import Panel
    from rich.table import Table

    console = Console()

    delta_color = "red" if diff["regression"] else "green"
    delta_sign = "+" if diff["score_delta"] > 0 else ""

    console.print(
        Panel(
            f"[bold]Target:[/bold] {diff['target_name']}\n"
            f"[bold]Score:[/bold] {diff['score_before']} → {diff['score_after']} "
            f"[{delta_color}]({delta_sign}{diff['score_delta']})[/{delta_color}]\n"
            f"[bold]Regression:[/bold] {'YES' if diff['regression'] else 'No'}",
            title=f"Session Diff: {diff['session_a'][:8]}.. → {diff['session_b'][:8]}..",
            border_style=delta_color,
        )
    )

    if diff["new_vulnerabilities"]:
        table = Table(title="New Vulnerabilities (Regressions)", show_lines=True)
        table.add_column("Category", style="red")
        table.add_column("Severity")
        for v in diff["new_vulnerabilities"]:
            table.add_row(v["category"], v["severity"])
        console.print(table)

    if diff["resolved_vulnerabilities"]:
        table = Table(title="Resolved Vulnerabilities", show_lines=True)
        table.add_column("Category", style="green")
        table.add_column("Severity")
        for v in diff["resolved_vulnerabilities"]:
            table.add_row(v["category"], v["severity"])
        console.print(table)

    console.print(
        f"\n  [dim]Persistent: {len(diff['persistent_vulnerabilities'])} | "
        f"New: {len(diff['new_vulnerabilities'])} | "
        f"Resolved: {len(diff['resolved_vulnerabilities'])}[/dim]\n"
    )


def run_watch(args):
    """Entry point for ``penbot watch``."""
    machine = is_machine(args)
    config_path = args.config
    interval = _parse_interval(args.interval)
    quick = getattr(args, "quick", False)

    if not machine:
        from rich.console import Console

        console = Console()
        console.print(
            f"\n  [bold blue]PenBot Watch[/bold blue]\n"
            f"  Config: {config_path}\n"
            f"  Interval: {args.interval} ({interval}s)\n"
            f"  Press Ctrl+C to stop.\n"
        )

    stop = False

    def _handle_signal(*_):
        nonlocal stop
        stop = True

    signal.signal(signal.SIGINT, _handle_signal)
    signal.signal(signal.SIGTERM, _handle_signal)

    run_count = 0
    previous_session_id = None

    while not stop:
        run_count += 1
        ts = datetime.now(timezone.utc).isoformat()

        if machine:
            emit_jsonl("watch_run_start", {"run": run_count, "timestamp": ts})
        else:
            print(f"  [{ts}] Run #{run_count}...")

        try:
            session_id = asyncio.run(_run_single_test(config_path, quick=quick, machine=machine))
        except Exception as e:
            if machine:
                emit_jsonl("watch_run_error", {"run": run_count, "error": str(e)})
            else:
                print(f"    Error: {e}")
            session_id = None

        if session_id and previous_session_id:
            # Compare with previous
            path_prev = SESSIONS_DIR / f"{previous_session_id}.json"
            path_curr = SESSIONS_DIR / f"{session_id}.json"

            if path_prev.exists() and path_curr.exists():
                data_prev = _load_session(path_prev)
                data_curr = _load_session(path_curr)
                if data_prev and data_curr:
                    diff = _compare_sessions(data_prev, data_curr)
                    if machine:
                        emit_jsonl("watch_diff", {"run": run_count, "diff": diff})
                    else:
                        delta = diff["score_delta"]
                        sign = "+" if delta > 0 else ""
                        new_count = len(diff["new_vulnerabilities"])
                        resolved_count = len(diff["resolved_vulnerabilities"])
                        print(
                            f"    Score: {diff['score_before']} → {diff['score_after']} "
                            f"({sign}{delta}) | "
                            f"New: {new_count} | Resolved: {resolved_count}"
                        )

        if session_id:
            previous_session_id = session_id

        if machine:
            emit_jsonl("watch_run_complete", {"run": run_count, "session_id": session_id})

        max_runs = getattr(args, "max_runs", 0)
        if max_runs and run_count >= max_runs:
            break

        if not stop:
            if not machine:
                print(f"    Sleeping {interval}s until next run...")
            time.sleep(interval)

    if machine:
        emit_jsonl("watch_stopped", {"total_runs": run_count})
    else:
        print(f"\n  Watch stopped after {run_count} runs.")
