"""nikame doctor — Diagnostic tool for NIKAME project health.

Verifies infrastructure status, migrations, credentials, and API health.
Provides actionable fix commands on failure.
"""

from __future__ import annotations

import os
import socket
import subprocess
import sys
from pathlib import Path
from typing import NamedTuple

import click
from rich.console import Console
from rich.table import Table

from nikame.utils.logger import console


class CheckResult(NamedTuple):
    name: str
    status: bool
    details: str
    fix: str = ""

@click.command()
@click.option(
    "--project-dir",
    type=click.Path(exists=True, path_type=Path),
    default=Path(".", "app").parent,
    help="Project directory to diagnose.",
)
def doctor(project_dir: Path) -> None:
    """Run deep diagnostics on the current NIKAME project."""
    console.print("[info]🏥 NIKAME Doctor is diagnosing your project...[/info]\n")
    
    if not (project_dir / "app").exists() and not (project_dir / "nikame.yaml").exists():
        console.print("[error]✗ Error: 'app/' directory or 'nikame.yaml' not found. Please run this command from the project root.[/error]")
        raise SystemExit(1)

    checks = [
        _check_python_version(),
        _check_docker_daemon(),
        _check_ports([8000, 5432]),
        _check_env_files(project_dir),
        _check_session_json(project_dir),
        _check_docker_compose(project_dir),
        _check_alembic(project_dir),
        _check_pytest(project_dir),
        _check_requirements(project_dir),
    ]

    table = Table(title="NIKAME Project Diagnostics")
    table.add_column("Status", justify="center")
    table.add_column("Check")
    table.add_column("Details")
    table.add_column("Suggested Fix", style="dim")

    all_passed = True
    for c in checks:
        icon = "[green]✓[/green]" if c.status else "[red]✗[/red]"
        if not c.status:
            all_passed = False
        table.add_row(icon, c.name, c.details, c.fix)

    console.print(table)
    
    if all_passed:
        console.print("\n[bold green]✨ Project is perfectly healthy! ✨[/bold green]")
    else:
        console.print("\n[bold red]⚠ Some checks failed. Please review the suggested fixes above.[/bold red]")


def _check_python_version() -> CheckResult:
    is_valid = sys.version_info >= (3, 11)
    return CheckResult(
        name="Python Version",
        status=is_valid,
        details=f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
        fix="Upgrade Python to >= 3.11" if not is_valid else ""
    )

def _check_docker_daemon() -> CheckResult:
    try:
        subprocess.run(["docker", "info"], check=True, capture_output=True)
        return CheckResult("Docker Daemon", True, "Running")
    except (subprocess.CalledProcessError, FileNotFoundError):
        return CheckResult("Docker Daemon", False, "Unreachable", "Start Docker Desktop or systemctl start docker")

def _check_ports(ports: list[int]) -> CheckResult:
    in_use = []
    for port in ports:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            if s.connect_ex(('localhost', port)) == 0:
                # 0 means connection succeeded -> port is LISTENING
                # We want the port to be available, meaning connection fails. Wait, if nikame up is running, they SHOULD be in use!
                # Ah, if nikame up is running, ports should be used by our docker compose.
                # If nikame up is NOT running, ports should be available.
                # Let's just report their status. This check can just warn if ports are used but docker isn't running ours.
                in_use.append(port)
                
    if in_use:
        return CheckResult("Port Availability", True, f"Ports in use: {in_use}", "Ensure these are your containers")
    return CheckResult("Port Availability", True, "Ports 8000, 5432 available for binding")

def _check_env_files(project_dir: Path) -> CheckResult:
    has_env = (project_dir / ".env").exists()
    has_generated = (project_dir / ".env.generated").exists()
    if has_env and has_generated:
        return CheckResult(".env Files", True, "Present")
    return CheckResult(
        ".env Files", 
        False, 
        "Missing .env or .env.generated", 
        "Run 'nikame up' or manually copy .env.generated to .env"
    )

def _check_session_json(project_dir: Path) -> CheckResult:
    # Look for .nikame/session.json
    has_session = (project_dir / ".nikame" / "session.json").exists()
    if has_session:
        return CheckResult(".nikame/session.json", True, "Present")
    return CheckResult(".nikame/session.json", False, "Missing", "Run 'nikame init' to generate a project session")

def _check_docker_compose(project_dir: Path) -> CheckResult:
    compose_file = project_dir / "infra" / "docker-compose.yml"
    if not compose_file.exists():
        return CheckResult("Docker Compose", False, "Missing infra/docker-compose.yml", "Run 'nikame up' or check config")
        
    try:
        result = subprocess.run(
            ["docker", "compose", "-f", str(compose_file), "ps", "--format", "json"],
            capture_output=True, text=True, check=True
        )
        import json
        services = json.loads(result.stdout) if result.stdout.strip() else []
        if not services:
            return CheckResult("Docker Compose", False, "No running containers", "Run 'nikame up'")
            
        running = sum(1 for s in services if "running" in s.get("Status", "").lower() or "up" in s.get("Status", "").lower())
        return CheckResult("Docker Compose", True, f"{running}/{len(services)} services up")
    except Exception as e:
        return CheckResult("Docker Compose", False, "Check failed", "Ensure Docker is running and compose file is valid")

def _check_alembic(project_dir: Path) -> CheckResult:
    if not (project_dir / "alembic.ini").exists():
        return CheckResult("Alembic Migrations", True, "Not configured (Skipping)")
        
    venv_bin = project_dir / ".venv" / ("Scripts" if os.name == "nt" else "bin")
    alembic_bin = venv_bin / "alembic"
    if not alembic_bin.exists():
        return CheckResult("Alembic Migrations", False, "Alembic not installed in venv", "Run 'nikame test' to provision .venv")
        
    # Deep check
    try:
        # Get head
        heads = subprocess.run([str(alembic_bin), "heads"], capture_output=True, text=True).stdout.strip()
        # Get current
        # Note: alembic current might fail if DB is down, which is fine
        current = subprocess.run([str(alembic_bin), "current"], capture_output=True, text=True).stdout.strip()
        
        if not heads:
             return CheckResult("Alembic Migrations", True, "No migrations defined")
        
        if "head" in current or (heads and heads.split()[0] in current):
            return CheckResult("Alembic Migrations", True, "Up to date")
        else:
            return CheckResult("Alembic Migrations", False, "Out of date", "Run 'nikame up' to apply migrations")
    except Exception:
        return CheckResult("Alembic Migrations", True, "Configured (Ready to test)")

def _check_pytest(project_dir: Path) -> CheckResult:
    venv_bin = project_dir / ".venv" / ("Scripts" if os.name == "nt" else "bin")
    pytest_bin = venv_bin / "pytest"
    if not pytest_bin.exists():
        return CheckResult("Pytest", False, "Not installed in venv", "Run 'nikame test' to provision .venv")
    return CheckResult("Pytest", True, "Installed")

def _check_requirements(project_dir: Path) -> CheckResult:
    reqs = ["requirements.txt"]
    missing = [r for r in reqs if not (project_dir / r).exists()]
    if missing:
        return CheckResult("Dependencies", False, f"Missing: {', '.join(missing)}", "Ensure project generation finished successfully")
        
    venv_dir = project_dir / ".venv"
    if not venv_dir.exists():
        return CheckResult("Dependencies", False, "No .venv found", "Run 'nikame test' to install dependencies")
        
    return CheckResult("Dependencies", True, "Requirements files and venv present")
