"""Click wrappers: tests + diagnose groups — split from ctl.py."""
from __future__ import annotations

import os
import sys
import json
import socket
import subprocess
from pathlib import Path
from urllib.parse import urlparse
from typing import NamedTuple

import click
import httpx

from proxym.cli import diagnostics as diag_cli
from proxym.cli._groups._shared import (
    make_args as _make_args,
    register_commands,
    passthrough,
    passthrough_fmt,
)

# Constants for check names
ENV_FILE = ".env file"
MASTER_KEY = "Master key"
PROXY_SERVER = "Proxy server"
DATA_DIR = "Data directory"
GIT_REPO = "Git repository"
DOCKER = "Docker"
NODEJS = "Node.js"
PYTHON_PKGS = "Python packages"
LLM_PROVIDERS = "LLM providers"
LLM_BACKEND = "LLM backend"
DATABASE = "Database"
OLLAMA = "Ollama"


# ── Tests group ──────────────────────────────────────────────

@click.group(name="tests")
def tests():
    """Run system diagnostics."""
    pass


register_commands(tests, [
    ("vscode",      diag_cli.cmd_test_vscode,      "Test VS Code Web connectivity."),
    ("proxy",       diag_cli.cmd_test_proxy,        "Test LLM proxy connectivity."),
    ("agent-setup", diag_cli.cmd_test_agent_setup,  "Comprehensive agent readiness check."),
    ("agent",       diag_cli.cmd_test_agent,         "Test agent with a simple completion."),
], passthrough)

register_commands(tests, [
    ("status",     diag_cli.cmd_tests_status,     "Run all diagnostic tests."),
    ("providers",  diag_cli.cmd_test_providers,   "Check configured LLM providers."),
    ("extensions", diag_cli.cmd_test_extensions,  "Check installed VS Code extensions."),
], passthrough_fmt)


# ── Diagnose alias group ─────────────────────────────────────

@click.group(name="diagnose")
def diagnose():
    """Run diagnostics to verify system health (alias for 'tests')."""
    pass


register_commands(diagnose, [
    ("proxy",  diag_cli.cmd_test_proxy,   "Test proxy connectivity."),
    ("vscode", diag_cli.cmd_test_vscode,  "Test VS Code Web status."),
    ("agent",  diag_cli.cmd_test_agent,   "Test agent setup + completion."),
], passthrough)

register_commands(diagnose, [
    ("all",       diag_cli.cmd_tests_status,   "Run all diagnostic tests."),
    ("providers", diag_cli.cmd_test_providers, "Test provider configuration."),
], passthrough_fmt)


@diagnose.command(name="test")
@click.argument("test_name")
@click.pass_context
def diagnose_one(ctx: click.Context, test_name: str):
    """Run a specific test (vscode|proxy|roo|extensions|providers|copilot|agent-setup|agent)."""
    from proxym.cli._client import make_client, print_json
    args = _make_args(ctx)
    c = make_client(args.proxy, args.key)
    r = c.get(f"/tests/{test_name}", timeout=30)
    print_json(r.json())


# ── Doctor command ───────────────────────────────────────────

class CheckResult(NamedTuple):
    name: str
    status: str
    message: str
    fix: str | None = None


def _load_env_vars() -> dict[str, str]:
    """Load environment variables from .env."""
    env_vars = {}
    env_path = Path(".env")
    if env_path.exists():
        content = env_path.read_text()
        for line in content.split('\n'):
            line = line.strip()
            if line and not line.startswith('#') and '=' in line:
                key, _, value = line.partition('=')
                env_vars[key] = value.strip().strip('"\'')
    return env_vars


def _check_env_file(env_vars: dict[str, str]) -> CheckResult:
    """Check .env file exists and has content."""
    env_path = Path(".env")
    if not env_path.exists():
        return CheckResult(ENV_FILE, "error", ".env file not found",
            "Create .env: cp .env.example .env && edit with your API keys")
    if not env_path.read_text().strip():
        return CheckResult(ENV_FILE, "error", ".env file is empty",
            "Add required configuration: AI_PROXY_MASTER_KEY and LLM provider keys")
    return CheckResult(ENV_FILE, "ok", f"Loaded {len(env_vars)} variables")


def _check_master_key(env_vars: dict[str, str]) -> CheckResult:
    """Check AI_PROXY_MASTER_KEY configuration."""
    key = env_vars.get("AI_PROXY_MASTER_KEY", "")
    if not key:
        return CheckResult(MASTER_KEY, "error", "AI_PROXY_MASTER_KEY missing",
            "Add to .env: AI_PROXY_MASTER_KEY=sk-proxy-...")
    if key.startswith("sk-or-"):
        return CheckResult(MASTER_KEY, "warning", "External OpenRouter key - SSE may not work",
            "Generate local key: proxym keys generate")
    if not key.startswith("sk-proxy-"):
        return CheckResult(MASTER_KEY, "warning", "Key format may be invalid",
            "Generate new key: proxym keys generate")
    return CheckResult(MASTER_KEY, "ok", "Local key format")


def _check_llm_providers(env_vars: dict[str, str]) -> CheckResult:
    """Check LLM provider API keys."""
    providers = [
        ("OPENROUTER_API_KEY", "OpenRouter"),
        ("ANTHROPIC_API_KEY", "Anthropic"),
        ("OPENAI_API_KEY", "OpenAI"),
        ("GOOGLE_AI_API_KEY", "Google AI"),
        ("MISTRAL_API_KEY", "Mistral"),
    ]
    configured = [name for key, name in providers if env_vars.get(key)]
    if not configured:
        return CheckResult(LLM_PROVIDERS, "error", "No provider API keys configured",
            "Add at least one: OPENROUTER_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.")
    return CheckResult(LLM_PROVIDERS, "ok", f"Configured: {', '.join(configured)}")


def _check_proxy_server(env_vars: dict[str, str]) -> CheckResult:
    """Check proxy server connectivity."""
    proxy_url = env_vars.get("PROXYM_DIAGNOSTICS_PROXY_URL", env_vars.get("AI_PROXY_URL", "http://localhost:4000"))
    try:
        parsed = urlparse(proxy_url)
        host = parsed.hostname or "localhost"
        port = parsed.port or 4000

        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(2)
        result = sock.connect_ex((host, port))
        sock.close()

        if result != 0:
            return CheckResult(PROXY_SERVER, "error", f"Not reachable at {host}:{port}",
                f"Start server: proxym serve (expected on {proxy_url})")

        try:
            r = httpx.get(f"{proxy_url}/health", timeout=5)
            if r.status_code == 200:
                version = r.json().get("version", "unknown")
                return CheckResult(PROXY_SERVER, "ok", f"Running (v{version})")
            return CheckResult(PROXY_SERVER, "warning", f"Health check failed (HTTP {r.status_code})",
                "Check server logs")
        except Exception:
            return CheckResult(PROXY_SERVER, "warning", "Port open but HTTP failed",
                "Server may be starting up - wait and retry")
    except Exception as e:
        return CheckResult(PROXY_SERVER, "error", f"Check failed: {e}",
            "Check AI_PROXY_URL format")


def _check_data_directory() -> CheckResult:
    """Check data directory exists and is writable."""
    data_dir = Path("data")
    if not data_dir.exists():
        return CheckResult(DATA_DIR, "warning", "Directory not found",
            "Create: mkdir data")
    if not os.access(data_dir, os.W_OK):
        return CheckResult(DATA_DIR, "error", "Not writable",
            "Fix permissions: chmod 755 data && chown $USER data")
    return CheckResult(DATA_DIR, "ok", f"Ready ({len(list(data_dir.iterdir()))} files)")


def _check_git_repository() -> CheckResult:
    """Check git repository status."""
    git_dir = Path(".git")
    if not git_dir.exists():
        return CheckResult(GIT_REPO, "warning", "Not a git repo - dashboard git sync won't work",
            "Initialize: git init && git add . && git commit -m 'initial'")

    try:
        result = subprocess.run(
            ["git", "log", "--oneline", "-1"],
            capture_output=True, text=True, timeout=2
        )
        if result.returncode == 0 and result.stdout.strip():
            return CheckResult(GIT_REPO, "ok", "Repository with commits")
        return CheckResult(GIT_REPO, "warning", "Git initialized but no commits",
            "Create initial commit: git add . && git commit -m 'initial'")
    except Exception:
        return CheckResult(GIT_REPO, "ok", "Repository present")


def _check_docker() -> CheckResult:
    """Check Docker availability."""
    try:
        result = subprocess.run(
            ["docker", "version", "--format", "{{.Server.Version}}"],
            capture_output=True, text=True, timeout=3
        )
        if result.returncode == 0 and result.stdout.strip():
            return CheckResult(DOCKER, "ok", f"Docker v{result.stdout.strip()}")
        return CheckResult(DOCKER, "warning", "Docker installed but daemon not running",
            "Start Docker: sudo systemctl start docker or launch Docker Desktop")
    except FileNotFoundError:
        return CheckResult(DOCKER, "info", "Not installed (optional)",
            "Install Docker for containerized tools")
    except Exception as e:
        return CheckResult(DOCKER, "warning", f"Docker check failed: {e}")


def _check_nodejs() -> CheckResult:
    """Check Node.js version."""
    try:
        result = subprocess.run(
            ["node", "--version"],
            capture_output=True, text=True, timeout=2
        )
        if result.returncode == 0:
            version = result.stdout.strip().lstrip('v')
            major = int(version.split('.')[0])
            if major >= 18:
                return CheckResult(NODEJS, "ok", f"v{version}")
            return CheckResult(NODEJS, "warning", f"v{version} (recommended: v18+)",
                "Upgrade: nvm install 20")
        return CheckResult(NODEJS, "info", "Not installed (optional for dashboard dev)")
    except FileNotFoundError:
        return CheckResult(NODEJS, "info", "Not installed (optional)")
    except Exception as e:
        return CheckResult(NODEJS, "warning", f"Check failed: {e}")


def _check_python_deps() -> CheckResult:
    """Check Python dependencies."""
    try:
        import proxym
        return CheckResult(PYTHON_PKGS, "ok", "proxym importable")
    except ImportError as e:
        return CheckResult(PYTHON_PKGS, "error", f"Cannot import proxym: {e}",
            "Install: pip install -e .")


def _check_ollama(env_vars: dict[str, str]) -> CheckResult | None:
    """Check Ollama if configured."""
    ollama_url = env_vars.get("OLLAMA_BASE_URL", "")
    if not ollama_url:
        return None
    try:
        r = httpx.get(f"{ollama_url}/api/tags", timeout=3)
        if r.status_code == 200:
            models = r.json().get("models", [])
            return CheckResult(OLLAMA, "ok", f"Running with {len(models)} models")
        return CheckResult(OLLAMA, "warning", f"Ollama returned HTTP {r.status_code}",
            "Check Ollama service")
    except Exception as e:
        return CheckResult(OLLAMA, "warning", f"Not reachable: {e}",
            "Start Ollama or check OLLAMA_BASE_URL")


def _check_llm_providers(env_vars: dict[str, str]) -> CheckResult:
    """Check individual LLM provider connectivity and configuration."""
    from proxym.config import get_settings
    settings = get_settings()
    
    providers = {
        "Anthropic": {
            "key": env_vars.get("ANTHROPIC_API_KEY", ""),
            "test_model": env_vars.get("PROXYM_TEST_MODEL_ANTHROPIC", settings.anthropic_cheap_model),
            "url": "https://api.anthropic.com/v1/messages"
        },
        "OpenRouter": {
            "key": env_vars.get("OPENROUTER_API_KEY", ""),
            "test_model": env_vars.get("PROXYM_TEST_MODEL_OPENROUTER", settings.openrouter_cheap_model),
            "url": "https://openrouter.ai/api/v1/chat/completions"
        },
        "OpenAI": {
            "key": env_vars.get("OPENAI_API_KEY", ""),
            "test_model": env_vars.get("PROXYM_TEST_MODEL_OPENAI", settings.openai_cheap_model),
            "url": "https://api.openai.com/v1/chat/completions"
        },
        "Google AI": {
            "key": env_vars.get("GOOGLE_AI_KEY", ""),
            "test_model": env_vars.get("PROXYM_TEST_MODEL_GOOGLE", settings.google_cheap_model),
            "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-latest:generateContent"
        },
        "DeepSeek": {
            "key": env_vars.get("DEEPSEEK_API_KEY", ""),
            "test_model": env_vars.get("PROXYM_TEST_MODEL_DEEPSEEK", settings.deepseek_cheap_model),
            "url": "https://api.deepseek.com/v1/chat/completions"
        },
        "Groq": {
            "key": env_vars.get("GROQ_API_KEY", ""),
            "test_model": env_vars.get("PROXYM_TEST_MODEL_GROQ", settings.groq_cheap_model),
            "url": "https://api.groq.com/openai/v1/chat/completions"
        },
        "Mistral": {
            "key": env_vars.get("MISTRAL_API_KEY", ""),
            "test_model": env_vars.get("PROXYM_TEST_MODEL_MISTRAL", settings.mistral_cheap_model),
            "url": "https://api.mistral.ai/v1/chat/completions"
        },
    }
    
    available = []
    invalid_keys = []
    missing_keys = []
    
    for name, config in providers.items():
        api_key = config["key"]
        if not api_key or not api_key.strip():
            missing_keys.append(name)
            continue
            
        # Validate API key format
        provider_key = name.lower().replace(" ", "")
        if not settings.validate_api_key(provider_key, api_key):
            invalid_keys.append(f"{name} (invalid key format)")
            continue
            
        available.append(f"{name} ✓")
    
    if available:
        message = f"{len(available)} providers configured: {', '.join(available)}"
        issues = []
        if invalid_keys:
            issues.append(f"Invalid keys: {', '.join(invalid_keys)}")
        if missing_keys:
            issues.append(f"Missing: {', '.join(missing_keys)}")
        
        fix = " | ".join(issues) if issues else "All providers configured"
        return CheckResult("LLM Providers", "ok", message, fix)
    else:
        return CheckResult("LLM Providers", "error", 
            "No valid LLM providers configured",
            f"Fix invalid keys: {', '.join(invalid_keys)} | Add missing: {', '.join(missing_keys)}" if invalid_keys or missing_keys else "Add API keys to .env")


def _check_llm_backend(env_vars: dict[str, str], checks: list[CheckResult]) -> CheckResult | None:
    """Check LLM backend connectivity with detailed provider diagnostics."""
    proxy_running = any(c.name == PROXY_SERVER and c.status == "ok" for c in checks)
    master_key = env_vars.get("AI_PROXY_MASTER_KEY", "")
    proxy_url = env_vars.get("PROXYM_DIAGNOSTICS_PROXY_URL", env_vars.get("AI_PROXY_URL", "http://localhost:4000"))

    if not proxy_running:
        return CheckResult(LLM_BACKEND, "info", "Skipped - proxy server not running",
            "Start server to test LLM: proxym serve")
    if not master_key:
        return CheckResult(LLM_BACKEND, "info", "Skipped - no master key",
            "Add AI_PROXY_MASTER_KEY to test LLM")

    # Detailed provider diagnostics
    from proxym.config import get_settings
    settings = get_settings()
    
    providers = {
        "Anthropic": env_vars.get("ANTHROPIC_API_KEY"),
        "OpenRouter": env_vars.get("OPENROUTER_API_KEY"), 
        "OpenAI": env_vars.get("OPENAI_API_KEY"),
        "Google AI": env_vars.get("GOOGLE_AI_KEY"),
        "DeepSeek": env_vars.get("DEEPSEEK_API_KEY"),
        "Groq": env_vars.get("GROQ_API_KEY"),
        "Mistral": env_vars.get("MISTRAL_API_KEY"),
    }
    
    available_providers = []
    failed_providers = []
    
    for name, api_key in providers.items():
        if api_key and api_key.strip():
            # Validate API key format
            provider_key = name.lower().replace(" ", "")
            if settings.validate_api_key(provider_key, api_key):
                available_providers.append(name)
            else:
                failed_providers.append(f"{name} (invalid key format)")
        else:
            failed_providers.append(f"{name} (no API key)")
    
    # Test alias routing
    try:
        # Test cheap alias
        r = httpx.post(
            f"{proxy_url}/v1/chat/completions",
            headers={"Authorization": f"Bearer {master_key}"},
            json={"model": "cheap", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 5},
            timeout=10
        )
        
        if r.status_code == 200:
            response_data = r.json()
            actual_model = response_data.get("model", "unknown")
            return CheckResult(LLM_BACKEND, "ok", 
                f"LLM API working - {len(available_providers)} providers configured, using: {actual_model}",
                f"Available: {', '.join(available_providers)}")
        else:
            error_detail = "Unknown error"
            try:
                error_data = r.json()
                if "detail" in error_data:
                    error_detail = error_data["detail"]
                    # Extract provider name from error
                    if "GeminiException" in error_detail:
                        error_detail = "Google AI: Invalid API key"
                    elif "AuthenticationError" in error_detail:
                        error_detail = f"Provider authentication failed"
            except:
                error_detail = f"HTTP {r.status_code}"
            
            return CheckResult(LLM_BACKEND, "error", f"LLM routing error: {error_detail}",
                f"Configured providers: {', '.join(available_providers)} | Missing: {', '.join(failed_providers)}")
                
    except httpx.ConnectError:
        return CheckResult(LLM_BACKEND, "error", "Cannot connect to proxy server",
            "Check if proxym serve is running")
    except Exception as e:
        return CheckResult(LLM_BACKEND, "warning", f"Test failed: {e}")


def _check_database() -> CheckResult:
    """Check SQLite database."""
    try:
        import sqlite3
        data_dir = Path("data")
        if not data_dir.exists():
            return CheckResult(DATABASE, "warning", "Data directory missing",
                "Create: mkdir data")
        db_files = list(data_dir.glob("*.sqlite")) + list(data_dir.glob("*.db"))
        if db_files:
            return CheckResult(DATABASE, "ok", f"SQLite DB found ({len(db_files)} file(s))")
        return CheckResult(DATABASE, "info", "No SQLite DB yet (will be created on first use)")
    except Exception as e:
        return CheckResult(DATABASE, "warning", f"SQLite check failed: {e}")


def _run_doctor_checks() -> list[CheckResult]:
    """Run comprehensive system diagnostics."""
    env_vars = _load_env_vars()
    checks: list[CheckResult] = []

    # Core checks
    checks.append(_check_env_file(env_vars))
    checks.append(_check_master_key(env_vars))
    checks.append(_check_llm_providers(env_vars))
    checks.append(_check_proxy_server(env_vars))
    checks.append(_check_data_directory())
    checks.append(_check_git_repository())

    # Optional/environment checks
    checks.append(_check_docker())
    checks.append(_check_nodejs())
    checks.append(_check_python_deps())

    # Conditional checks
    ollama_check = _check_ollama(env_vars)
    if ollama_check:
        checks.append(ollama_check)

    # Check individual LLM providers
    providers_check = _check_llm_providers(env_vars)
    checks.append(providers_check)

    llm_check = _check_llm_backend(env_vars, checks)
    if llm_check:
        checks.append(llm_check)

    checks.append(_check_database())

    return checks


def _print_doctor_report(checks: list[CheckResult]):
    """Print diagnostic report."""
    print("\n" + "═" * 60)
    print("  🔍 PROXYM SYSTEM DIAGNOSTICS")
    print("═" * 60 + "\n")

    ok = [c for c in checks if c.status == "ok"]
    warnings = [c for c in checks if c.status == "warning"]
    errors = [c for c in checks if c.status == "error"]

    if errors:
        print(f"\033[0;31m✗ {len(errors)} CRITICAL ISSUE(S)\033[0m")
        for check in errors:
            print(f"\n  \033[1m{check.name}\033[0m")
            print(f"  → {check.message}")
            if check.fix:
                print(f"  \033[2m  Fix: {check.fix}\033[0m")
        print()

    if warnings:
        print(f"\033[0;33m⚠ {len(warnings)} WARNING(S)\033[0m")
        for check in warnings:
            print(f"\n  \033[1m{check.name}\033[0m")
            print(f"  → {check.message}")
            if check.fix:
                print(f"  \033[2m  Fix: {check.fix}\033[0m")
        print()

    if ok:
        print(f"\033[0;32m✓ {len(ok)} CHECK(S) PASSED\033[0m")
        for check in ok:
            print(f"  • {check.name}: {check.message}")
        print()

    print("─" * 60)
    if errors:
        print("\033[0;31mStatus: NOT READY - fix critical issues\033[0m")
    elif warnings:
        print("\033[0;33mStatus: FUNCTIONAL with warnings\033[0m")
    else:
        print("\033[0;32mStatus: ALL SYSTEMS OPERATIONAL\033[0m")
    print("─" * 60 + "\n")


@click.command(name="doctor")
def doctor_cmd():
    """Comprehensive system diagnostics — check configuration and health.

    Checks:
      • .env file and API keys
      • Proxy server connectivity
      • LLM provider configuration
      • Data directory and git repository
      • Docker, Node.js, Python dependencies
      • LLM backend test (if server running)

    Run this first if you're experiencing issues.
    """
    checks = _run_doctor_checks()
    _print_doctor_report(checks)

    errors = [c for c in checks if c.status == "error"]
    if errors:
        print("Quick actions:")
        print("  proxym keys init     # Initialize API keys")
        print("  proxym serve         # Start the proxy server")
        sys.exit(1)
