"""AutoDebug doctor - environment diagnostics."""

from __future__ import annotations

import os
import shutil
import subprocess
import sys
from importlib import metadata
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

import requests

from autodebug.config import load_credentials

# Minimum supported Python version
MIN_PYTHON_VERSION = (3, 9)

# Known dependency support levels
DEPENDENCY_SUPPORT = {
    # Fully supported
    "requests": {"status": "supported", "message": "HTTP client (sync)"},
    "httpx": {"status": "supported", "message": "HTTP client (sync)"},
    "psycopg2": {"status": "supported", "message": "PostgreSQL driver"},
    "psycopg2-binary": {"status": "supported", "message": "PostgreSQL driver"},
    "psycopg": {"status": "supported", "message": "PostgreSQL driver (v3)"},
    "pytest": {"status": "supported", "message": "Testing framework"},
    "redis": {"status": "supported", "message": "Redis client (sync)"},
    "fakeredis": {"status": "supported", "message": "Redis mock for testing"},
    "aiohttp": {"status": "supported", "message": "HTTP client (async)"},

    # Partial support
    "sqlalchemy": {"status": "partial", "message": "ORM - capture at DBAPI layer only"},
    "anyio": {"status": "partial", "message": "Async support experimental"},
    "grpcio": {"status": "partial", "message": "gRPC - unary-unary only (streaming not supported)"},
    "boto3": {"status": "partial", "message": "AWS SDK - S3 read operations only"},
    "botocore": {"status": "partial", "message": "AWS SDK - S3 read operations only"},
    "pymongo": {"status": "partial", "message": "MongoDB - read operations only"},

    # Not yet supported
    "asyncpg": {"status": "unsupported", "message": "Async PostgreSQL not yet supported"},
    "celery": {"status": "unsupported", "message": "Task queue not yet supported"},
    "elasticsearch": {"status": "unsupported", "message": "Elasticsearch not yet supported"},
    "trio": {"status": "unsupported", "message": "Trio runtime not yet supported"},
}

# Packages that can conflict with I/O capture
CONFLICTING_PACKAGES = [
    "pytest-recording",
    "vcrpy",
    "responses",
    "requests-mock",
    "httpretty",
    "respx",
]

# CI environment detection
CI_ENVIRONMENTS = {
    "CI": "Generic CI",
    "GITHUB_ACTIONS": "GitHub Actions",
    "GITLAB_CI": "GitLab CI",
    "CIRCLECI": "CircleCI",
    "JENKINS_URL": "Jenkins",
    "TRAVIS": "Travis CI",
    "BUILDKITE": "Buildkite",
    "CODEBUILD_BUILD_ID": "AWS CodeBuild",
    "TF_BUILD": "Azure DevOps",
}


class Check:
    """A single diagnostic check."""

    def __init__(self, name: str, passed: bool, message: str, detail: Optional[str] = None, warning: bool = False):
        self.name = name
        self.passed = passed
        self.warning = warning
        self.message = message
        self.detail = detail

    @property
    def symbol(self) -> str:
        if self.warning:
            return "⚠"
        return "✓" if self.passed else "✗"

    def __str__(self) -> str:
        result = f"{self.symbol} {self.message}"
        if self.detail:
            result += f"\n    {self.detail}"
        return result


def check_python_version() -> Check:
    """Check Python version is supported."""
    version = sys.version_info[:2]
    passed = version >= MIN_PYTHON_VERSION
    message = f"Python {sys.version.split()[0]}"
    if passed:
        message += f" (supported: {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]}+)"
    else:
        message += f" (requires {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]}+)"
    return Check("python", passed, message)


def check_docker() -> Check:
    """Check Docker is available."""
    docker_path = shutil.which("docker")
    if not docker_path:
        return Check("docker", False, "Docker not found in PATH")

    try:
        result = subprocess.run(
            ["docker", "version", "--format", "{{.Server.Version}}"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if result.returncode == 0:
            version = result.stdout.strip()
            return Check("docker", True, f"Docker {version} available")
        else:
            return Check(
                "docker",
                False,
                "Docker found but not running",
                detail="Start Docker Desktop or docker daemon",
            )
    except subprocess.TimeoutExpired:
        return Check("docker", False, "Docker command timed out")
    except Exception as e:
        return Check("docker", False, f"Docker check failed: {e}")


from autodebug.repo_utils import _normalize_repo_slug  # noqa: E402  # consolidated


def _detect_repo_slug() -> Optional[str]:
    try:
        for name in ("origin", "upstream"):
            result = subprocess.run(
                ["git", "remote", "get-url", name],
                capture_output=True,
                text=True,
                check=False,
                timeout=3,
            )
            if result.returncode == 0:
                slug = _normalize_repo_slug(result.stdout.strip())
                if slug:
                    return slug
    except Exception:
        return None
    return None


def _auto_link_project(creds: Dict[str, Any], token: str) -> tuple[Optional[str], Optional[str]]:
    """Best-effort read-only project lookup.

    Important: doctor checks must be side-effect free. This function does not
    create projects or write credentials.
    """
    from autodebug.config import get_api_base

    api_base = creds.get("api_base") or get_api_base()
    repo_slug = creds.get("repo_slug") or _detect_repo_slug()
    if not repo_slug:
        return None, "Repo slug not detected"

    try:
        resp = requests.get(
            f"{api_base.rstrip('/')}/v1/projects?limit=200&offset=0",
            headers={"Authorization": f"Bearer {token}"},
            timeout=10,
        )
        if resp.status_code == 200:
            for proj in resp.json() or []:
                if str(proj.get("repo", "")).lower() == repo_slug.lower():
                    pid = proj.get("id") or proj.get("project_id")
                    if pid:
                        return pid, None
        else:
            return None, f"Project lookup failed ({resp.status_code})"
        return None, "No matching project found for detected repository slug"
    except Exception as e:
        return None, f"Auto-link error: {e}"


def check_network(api_base: Optional[str] = None) -> Check:
    """Check network connectivity to API."""
    if not api_base:
        from autodebug.config import get_api_base
        api_base = get_api_base()

    if not api_base:
        return Check(
            "network",
            False,
            "API endpoint not configured",
            detail="Run: hikaflow init --api-base <url>",
        )

    try:
        response = requests.get(f"{api_base.rstrip('/')}/health", timeout=5)
        if response.status_code < 500:
            return Check("network", True, f"Network access to {api_base}")
        else:
            return Check("network", False, f"API returned error: {response.status_code}")
    except requests.exceptions.ConnectionError:
        return Check(
            "network",
            False,
            f"Cannot connect to {api_base}",
            detail="Check your network connection and firewall settings",
        )
    except requests.exceptions.Timeout:
        return Check(
            "network",
            False,
            f"Connection to {api_base} timed out",
            detail="The API may be down or your connection may be slow",
        )
    except Exception as e:
        return Check("network", False, f"Network check failed: {e}")


def check_credentials() -> Check:
    """Check credentials are configured."""
    import concurrent.futures

    from autodebug.config import get_valid_token
    creds = load_credentials()

    # get_valid_token may attempt network refresh; cap at 10s
    with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
        future = pool.submit(get_valid_token)
        try:
            token = future.result(timeout=3)
        except concurrent.futures.TimeoutError:
            return Check(
                "credentials",
                False,
                "Credential check timed out",
                detail="Token refresh is slow. Run: hikaflow login --force",
            )
        except Exception:
            token = None

    if not token:
        return Check(
            "credentials",
            False,
            "No authentication token",
            detail="Run: hikaflow login",
        )

    project_id = creds.get("project_id")
    if not project_id and token:
        project_id, reason = _auto_link_project(creds, token)
        if project_id:
            return Check("credentials", True, "Credentials configured")
        if reason:
            return Check(
                "credentials",
                False,
                "No project configured",
                detail=f"Project lookup failed: {reason}",
            )

    if not project_id:
        return Check(
            "credentials",
            False,
            "No project configured",
            detail="Run: hikaflow init --project-id <id>",
        )

    return Check("credentials", True, "Credentials configured")


def check_disk_space() -> Check:
    """Check available disk space for captures."""
    try:
        usage = shutil.disk_usage(".")
        free_gb = usage.free / (1024 ** 3)
        if free_gb < 1.0:
            return Check(
                "disk",
                False,
                f"Low disk space: {free_gb:.1f} GB free",
                detail="Hikaflow captures can be 100MB+. Free up disk space.",
            )
        return Check("disk", True, f"Disk space: {free_gb:.1f} GB free")
    except Exception as e:
        return Check("disk", True, f"Could not check disk space: {e}")


def check_ci_environment() -> Check:
    """Detect CI environment and suggest optimizations."""
    for var, name in CI_ENVIRONMENTS.items():
        if os.environ.get(var):
            return Check(
                "ci",
                True,
                f"CI detected: {name}",
                detail="Use HIKAFLOW_TOKEN env var for auth. Use --headless for auto-debug.",
            )
    return Check("ci", True, "Not running in CI")


def check_conflicting_packages() -> Check:
    """Check for packages that may interfere with I/O capture."""
    installed_conflicts = []
    for pkg in CONFLICTING_PACKAGES:
        try:
            metadata.version(pkg)
            installed_conflicts.append(pkg)
        except metadata.PackageNotFoundError:
            pass

    if installed_conflicts:
        return Check(
            "conflicts",
            True,
            f"Potentially conflicting packages: {', '.join(installed_conflicts)}",
            detail="These packages mock HTTP and may interfere with capture. Disable them during hikaflow runs.",
            warning=True,
        )
    return Check("conflicts", True, "No conflicting packages found")


def check_config_file() -> Check:
    """Check for project configuration file."""
    for name in [".autodebug.yml", ".autodebug.yaml", "autodebug.yml"]:
        if (Path.cwd() / name).exists():
            return Check("config", True, f"Config file: {name}")
    return Check(
        "config",
        True,
        "No config file (using defaults)",
        detail="Create one with: hikaflow init",
    )


def check_dependencies() -> Tuple[List[Check], Dict[str, List[str]]]:
    """Check installed dependencies for support status."""
    checks = []
    by_status: Dict[str, List[str]] = {
        "supported": [],
        "partial": [],
        "unsupported": [],
        "unknown": [],
    }

    installed = {}
    _MAX_PACKAGES = 500  # Safety cap to avoid slow iteration on large envs
    for i, dist in enumerate(metadata.distributions()):
        if i >= _MAX_PACKAGES:
            break
        name = dist.metadata.get("Name")
        version = dist.version
        if name and version:
            installed[name.lower()] = version

    for pkg_name, info in DEPENDENCY_SUPPORT.items():
        pkg_lower = pkg_name.lower()
        if pkg_lower in installed:
            version = installed[pkg_lower]
            status = info["status"]
            by_status[status].append(f"{pkg_name} {version}")

    if by_status["supported"]:
        checks.append(Check(
            "deps_supported",
            True,
            f"{len(by_status['supported'])} supported dependencies",
            detail=", ".join(by_status["supported"][:5]) + ("..." if len(by_status["supported"]) > 5 else ""),
        ))

    if by_status["partial"]:
        checks.append(Check(
            "deps_partial",
            True,
            f"{len(by_status['partial'])} partially supported",
            detail=", ".join(by_status["partial"]),
        ))

    if by_status["unsupported"]:
        checks.append(Check(
            "deps_unsupported",
            False,
            f"{len(by_status['unsupported'])} unsupported {'dependency' if len(by_status['unsupported']) == 1 else 'dependencies'}",
            detail=", ".join(by_status["unsupported"]),
        ))

    return checks, by_status


def run_doctor(verbose: bool = False) -> Dict[str, Any]:
    """Run all doctor checks and return results."""
    results = {
        "checks": [],
        "passed": 0,
        "failed": 0,
        "dependencies": {},
    }

    # Core checks
    core_checks = [
        check_python_version(),
        check_docker(),
        check_credentials(),
    ]

    for check in core_checks:
        results["checks"].append(check)
        if check.passed:
            results["passed"] += 1
        else:
            results["failed"] += 1

    # Network check
    network = check_network()
    results["checks"].append(network)
    if network.passed:
        results["passed"] += 1
    else:
        results["failed"] += 1

    # New checks: disk space, CI, conflicts, config file
    extra_checks = [
        check_disk_space(),
        check_ci_environment(),
        check_conflicting_packages(),
        check_config_file(),
    ]

    for check in extra_checks:
        results["checks"].append(check)
        if check.passed:
            results["passed"] += 1
        else:
            results["failed"] += 1

    # Dependency checks
    dep_checks, by_status = check_dependencies()
    results["dependencies"] = by_status
    for check in dep_checks:
        results["checks"].append(check)
        if check.passed:
            results["passed"] += 1
        else:
            results["failed"] += 1

    return results


def format_report(results: Dict[str, Any]) -> str:
    """Format doctor results as a string (plain text fallback)."""
    lines = [
        "",
        "Hikaflow Environment Check",
        "===========================",
        "",
    ]

    for check in results["checks"]:
        lines.append(str(check))

    lines.append("")
    lines.append(f"Summary: {results['passed']} passed, {results['failed']} failed")

    recommendations = []
    for check in results["checks"]:
        if not check.passed and check.detail:
            recommendations.append(check.detail)

    if recommendations:
        lines.append("")
        lines.append("Recommendations:")
        for rec in recommendations:
            lines.append(f"  • {rec}")

    deps = results.get("dependencies", {})
    if deps.get("unsupported"):
        lines.append("")
        lines.append("Note: Unsupported dependencies will be flagged in capture.")
        lines.append("Tests using these libraries will report FAILED_UNSUPPORTED status.")

    return "\n".join(lines)


def format_report_rich(results: Dict[str, Any]) -> None:
    """Format doctor results using Rich for colored terminal output."""
    from rich.console import Console
    from rich.panel import Panel

    console = Console()

    console.print()
    console.print("[bold green]Hikaflow Environment Check[/]")
    console.print()

    warnings = 0
    for check in results["checks"]:
        if check.warning:
            symbol = "[yellow]⚠[/]"
            warnings += 1
        elif check.passed:
            symbol = "[green]✓[/]"
        else:
            symbol = "[red]✗[/]"
        console.print(f"  {symbol} {check.message}")
        if check.detail:
            console.print(f"      [dim]{check.detail}[/]")

    console.print()
    passed = results["passed"]
    failed = results["failed"]
    parts = [f"{passed} passed"]
    if failed > 0:
        parts.append(f"[red]{failed} failed[/]")
    if warnings > 0:
        parts.append(f"[yellow]{warnings} warnings[/]")
    console.print(f"Summary: {', '.join(parts)}")

    # Recommendations panel
    recommendations = []
    for check in results["checks"]:
        if (not check.passed or check.warning) and check.detail:
            recommendations.append(f"  • {check.detail}")

    if recommendations:
        console.print()
        console.print(Panel(
            "\n".join(recommendations),
            title="Recommendations",
            border_style="yellow",
        ))
