"""
Diagnostic health-check for a PenBot installation.

`penbot doctor` inspects the local environment and prints a
pass/warn/fail status for every component PenBot depends on.
"""

import contextlib
import importlib
import io
import os
import platform
import subprocess
import sys
from pathlib import Path

from rich.console import Console
from rich.panel import Panel
from rich.table import Table

console = Console()

PASS = "[bold green]PASS[/bold green]"
WARN = "[bold yellow]WARN[/bold yellow]"
FAIL = "[bold red]FAIL[/bold red]"


def run_doctor(args):
    """Entry point for `penbot doctor`."""
    from src.cli.machine_output import emit_json, is_machine

    machine = is_machine(args)

    if not machine:
        console.print(
            Panel.fit(
                "[bold blue]PenBot Doctor[/bold blue]\n" "[dim]Checking your environment...[/dim]",
                border_style="blue",
            )
        )

    results: list[tuple[str, str, str]] = []  # (check_name, status, detail)

    results.append(_check_python_version())
    results.extend(_check_core_packages())
    results.extend(_check_api_keys())
    results.append(_check_playwright_browsers())
    results.append(_check_env_file())
    results.append(_check_config_files())
    results.append(_check_cookie_dir())
    results.append(_check_sessions_db())

    # Machine-readable output
    if machine:
        _status_map = {PASS: "pass", WARN: "warn", FAIL: "fail"}

        def _resolve(s):
            return _status_map.get(s, "unknown")

        checks = [
            {"check": name, "status": _resolve(status), "detail": detail}
            for name, status, detail in results
        ]
        passes = sum(1 for c in checks if c["status"] == "pass")
        warns = sum(1 for c in checks if c["status"] == "warn")
        fails = sum(1 for c in checks if c["status"] == "fail")
        emit_json(
            {
                "checks": checks,
                "summary": {"pass": passes, "warn": warns, "fail": fails},
                "healthy": fails == 0,
            }
        )
        return

    # Human-readable output
    table = Table(title="Diagnostic Results", show_lines=True, padding=(0, 1))
    table.add_column("Check", style="bold", min_width=30)
    table.add_column("Status", justify="center", min_width=6)
    table.add_column("Detail", min_width=40)

    passes = warns = fails = 0
    for name, status, detail in results:
        table.add_row(name, status, detail)
        if "PASS" in status:
            passes += 1
        elif "WARN" in status:
            warns += 1
        else:
            fails += 1

    console.print()
    console.print(table)

    summary_parts = [f"[green]{passes} passed[/green]"]
    if warns:
        summary_parts.append(f"[yellow]{warns} warnings[/yellow]")
    if fails:
        summary_parts.append(f"[red]{fails} failed[/red]")
    console.print(f"\n  {' / '.join(summary_parts)}")

    if fails:
        console.print("\n  Run [cyan]penbot onboard[/cyan] to fix common issues.\n")
    elif warns:
        console.print("\n  Everything essential looks good. Warnings are optional.\n")
    else:
        console.print("\n  [bold green]All checks passed. You're ready to test.[/bold green]\n")


# ------------------------------------------------------------------
# Individual checks
# ------------------------------------------------------------------


def _check_python_version() -> tuple[str, str, str]:
    ver = platform.python_version()
    major, minor = sys.version_info[:2]
    if major >= 3 and minor >= 11:
        return ("Python version", PASS, f"{ver}")
    return ("Python version", FAIL, f"{ver} — PenBot requires Python >= 3.11")


def _check_core_packages() -> list[tuple[str, str, str]]:
    """Verify critical packages are importable."""
    results = []
    core = [
        ("langgraph", "langgraph"),
        ("langchain", "langchain"),
        ("langchain-anthropic", "langchain_anthropic"),
        ("pydantic", "pydantic"),
        ("rich", "rich"),
        ("httpx", "httpx"),
        ("structlog", "structlog"),
        ("pyyaml", "yaml"),
    ]
    optional = [
        ("playwright", "playwright"),
        ("fastapi", "fastapi"),
        ("tavily-python", "tavily"),
        ("weasyprint", "weasyprint"),
    ]

    for label, module_name in core:
        try:
            mod = importlib.import_module(module_name)
            ver = getattr(mod, "__version__", "installed")
            results.append((f"Package: {label}", PASS, str(ver)))
        except ImportError:
            results.append((f"Package: {label}", FAIL, "not installed (required)"))

    for label, module_name in optional:
        try:
            with (
                contextlib.redirect_stdout(io.StringIO()),
                contextlib.redirect_stderr(io.StringIO()),
            ):
                mod = importlib.import_module(module_name)
            ver = getattr(mod, "__version__", "installed")
            results.append((f"Package: {label}", PASS, str(ver)))
        except (ImportError, OSError):
            results.append((f"Package: {label}", WARN, "not installed (optional)"))

    return results


def _check_api_keys() -> list[tuple[str, str, str]]:
    """Check that at least one LLM API key is configured."""
    results = []

    # Load .env values too (user may not have exported them)
    env_vals = _load_dot_env()

    anthropic = os.getenv("ANTHROPIC_API_KEY") or env_vals.get("ANTHROPIC_API_KEY", "")
    openai = os.getenv("OPENAI_API_KEY") or env_vals.get("OPENAI_API_KEY", "")
    tavily = os.getenv("TAVILY_API_KEY") or env_vals.get("TAVILY_API_KEY", "")

    def _is_placeholder(v: str) -> bool:
        return not v or "your-key-here" in v or "your_key" in v or v.strip() == ""

    if not _is_placeholder(anthropic):
        masked = anthropic[:10] + "..." + anthropic[-4:]
        results.append(("API Key: Anthropic", PASS, masked))
    else:
        results.append(("API Key: Anthropic", WARN, "not set"))

    if not _is_placeholder(openai):
        masked = openai[:7] + "..." + openai[-4:]
        results.append(("API Key: OpenAI", PASS, masked))
    else:
        results.append(("API Key: OpenAI", WARN, "not set"))

    # At least one must be set
    if _is_placeholder(anthropic) and _is_placeholder(openai):
        results.append(("LLM Provider", FAIL, "No LLM API key configured — PenBot cannot run"))
    else:
        results.append(("LLM Provider", PASS, "at least one key available"))

    if not _is_placeholder(tavily):
        masked = tavily[:8] + "..." + tavily[-4:]
        results.append(("API Key: Tavily", PASS, masked))
    else:
        results.append(("API Key: Tavily", WARN, "not set (reconnaissance disabled)"))

    return results


def _check_playwright_browsers() -> tuple[str, str, str]:
    """Check if Playwright Chromium is installed."""
    try:
        import playwright  # noqa: F401
    except ImportError:
        return ("Playwright Browsers", WARN, "playwright package not installed")

    try:
        result = subprocess.run(
            [sys.executable, "-m", "playwright", "install", "--dry-run"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        # `--dry-run` may not be supported; heuristic: check if "chromium" appears
        # in the output as "already installed" or similar
        output = result.stdout + result.stderr
        if "chromium" in output.lower() and "already" in output.lower():
            return ("Playwright Browsers", PASS, "Chromium installed")
    except Exception:
        pass

    # Fallback: try to locate browser binaries
    try:
        from playwright._impl._driver import compute_driver_executable

        driver = Path(compute_driver_executable())
        browsers_dir = driver.parent / ".local-browsers"
        if browsers_dir.exists() and any(browsers_dir.iterdir()):
            return ("Playwright Browsers", PASS, "browser binaries found")
    except Exception:
        pass

    # Second fallback: check common location
    home = Path.home()
    common_paths = [
        home / "Library" / "Caches" / "ms-playwright",  # macOS
        home / ".cache" / "ms-playwright",  # Linux
        home / "AppData" / "Local" / "ms-playwright",  # Windows
    ]
    for p in common_paths:
        if p.exists() and any(p.iterdir()):
            return ("Playwright Browsers", PASS, f"found at {p}")

    return (
        "Playwright Browsers",
        WARN,
        "not found — run 'playwright install chromium'",
    )


def _check_env_file() -> tuple[str, str, str]:
    if Path(".env").exists():
        size = Path(".env").stat().st_size
        return (".env file", PASS, f"exists ({size} bytes)")
    return (".env file", WARN, "missing — run 'penbot onboard'")


def _check_config_files() -> tuple[str, str, str]:
    config_dir = Path("configs/clients")
    if not config_dir.exists():
        return ("Target configs", WARN, "configs/clients/ not found")
    yamls = [f for f in config_dir.glob("*.yaml") if f.name != "_TEMPLATE_FULL.yaml"]
    if yamls:
        names = ", ".join(f.stem for f in yamls[:5])
        suffix = f" (+{len(yamls) - 5} more)" if len(yamls) > 5 else ""
        return ("Target configs", PASS, f"{len(yamls)} found: {names}{suffix}")
    return ("Target configs", WARN, "no configs yet — run 'penbot wizard'")


def _check_cookie_dir() -> tuple[str, str, str]:
    cookie_dir = Path(".penbot/sessions")
    if cookie_dir.exists():
        cookies = list(cookie_dir.glob("*_cookies.json"))
        return ("Session cookies", PASS, f"{len(cookies)} saved session(s)")
    return ("Session cookies", PASS, "no saved sessions (clean slate)")


def _check_sessions_db() -> tuple[str, str, str]:
    db_path = Path("pentest.db")
    if db_path.exists():
        size_kb = db_path.stat().st_size / 1024
        return ("Sessions database", PASS, f"pentest.db ({size_kb:.0f} KB)")
    return ("Sessions database", PASS, "not created yet (will be on first run)")


# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------


def _load_dot_env() -> dict:
    """Minimal .env parser (no dependency on python-dotenv at import time)."""
    vals = {}
    env_path = Path(".env")
    if not env_path.exists():
        return vals
    for line in env_path.read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if line.startswith("#") or "=" not in line:
            continue
        k, _, v = line.partition("=")
        vals[k.strip()] = v.strip().strip('"').strip("'")
    return vals
