"""Core system commands: version, info, diagnostics.

Implements the CLI0.core group per specification.  Each command supports
``--format`` for output parity (rich/plain/json/yaml) and ``diagnostics``
additionally supports ``--check`` to exit non-zero when any check fails.
"""

from __future__ import annotations

import os
import platform
import shutil
import subprocess
import sys
import time
from datetime import UTC, datetime
from enum import StrEnum
from pathlib import Path
from typing import Any

from sqlalchemy import create_engine
from sqlalchemy import inspect as sa_inspect

from cleveragents import __version__
from cleveragents.application.container import get_database_url
from cleveragents.application.services.lock_service import LockService

# ---------------------------------------------------------------------------
# Diagnostic check status
# ---------------------------------------------------------------------------


class CheckStatus(StrEnum):
    """Status of a single diagnostic check."""

    OK = "ok"
    WARN = "warn"
    ERROR = "error"


# ---------------------------------------------------------------------------
# Data builders (format-agnostic)
# ---------------------------------------------------------------------------


def _git_sha() -> str:
    """Return the short git SHA of HEAD.

    Tries in order:
    1. CLEVERAGENTS_COMMIT environment variable (set at build time)
    2. git rev-parse (if running from source)
    3. Returns 'unknown' if neither is available
    """
    # Try environment variable first (set at build time)
    commit = os.environ.get("CLEVERAGENTS_COMMIT", "").strip()
    if commit:
        return commit

    # Fall back to git command (when running from source)
    try:
        result = subprocess.run(
            ["git", "rev-parse", "--short", "HEAD"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if result.returncode == 0:
            return result.stdout.strip()
    except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
        pass

    return "unknown"


def _dep_version(package: str) -> str:
    """Return the installed version of *package*, or 'not installed'."""
    try:
        from importlib.metadata import version as pkg_version

        return pkg_version(package)
    except Exception:
        return "not installed"


def build_version_data() -> dict[str, Any]:
    """Assemble structured data for the ``version`` command."""
    return {
        "version": __version__,
        "channel": "stable",
        "python": platform.python_version(),
        "build_date": datetime.now(tz=UTC).strftime("%Y-%m-%d"),
        "commit": _git_sha(),
        "schema": "v3",
        "platform": f"{sys.platform}-{platform.machine()}",
        "dependencies": {
            "langgraph": _dep_version("langgraph"),
            "langchain-core": _dep_version("langchain-core"),
            "pydantic": _dep_version("pydantic"),
            "typer": _dep_version("typer"),
        },
    }


def build_info_data() -> dict[str, Any]:
    """Assemble structured data for the ``info`` command."""
    from cleveragents.config.settings import get_settings

    settings = get_settings()

    configured_providers = settings.configured_provider_names()
    data_dir = settings.data_dir
    config_path = Path(
        os.environ.get(
            "CLEVERAGENTS_CONFIG_PATH",
            str(settings.storage_path / "config.toml"),
        )
    )
    db_url = settings.database_url

    # Storage sizes (best effort)
    storage: dict[str, str] = {}
    try:
        db_path_str = db_url.replace("sqlite:///", "")
        db_path = Path(db_path_str)
        if db_path.exists():
            size_mb = db_path.stat().st_size / (1024 * 1024)
            storage["db_size"] = f"{size_mb:.1f} MB"
        else:
            storage["db_size"] = "0 MB"
    except Exception:
        storage["db_size"] = "unknown"

    log_dir = settings.log_dir
    if log_dir.exists():
        total = sum(f.stat().st_size for f in log_dir.rglob("*") if f.is_file())
        storage["logs"] = f"{total / (1024 * 1024):.1f} MB"
    else:
        storage["logs"] = "0 MB"

    from cleveragents.cli.commands.server import resolve_server_mode

    server_mode = resolve_server_mode()

    return {
        "version": __version__,
        "data_dir": str(data_dir),
        "config_path": str(config_path),
        "database": db_url,
        "server_mode": server_mode,
        "platform": f"{platform.system()} {platform.release()} ({platform.machine()})",
        "automation": settings.default_automation_profile,
        "providers_configured": len(configured_providers),
        "providers": configured_providers,
        "debug_mode": settings.debug_enabled,
        "storage": storage,
    }


def _check_config_file() -> dict[str, Any]:
    """Check that the config path is readable."""
    config_path = Path(
        os.environ.get(
            "CLEVERAGENTS_CONFIG_PATH",
            "config.toml",
        )
    )
    if config_path.exists():
        readable = os.access(config_path, os.R_OK)
        return {
            "name": "Config file",
            "status": CheckStatus.OK if readable else CheckStatus.ERROR,
            "details": "readable" if readable else "not readable",
        }
    return {
        "name": "Config file",
        "status": CheckStatus.OK,
        "details": "not present (using defaults)",
    }


def _check_data_dir() -> dict[str, Any]:
    """Check that the data directory exists and is writable."""
    from cleveragents.config.settings import get_settings

    settings = get_settings()
    data_dir = settings.data_dir

    if not data_dir.exists():
        return {
            "name": "Data directory",
            "status": CheckStatus.WARN,
            "details": f"missing ({data_dir})",
            "recommendation": f"Create it with: mkdir -p {data_dir}",
        }

    writable = os.access(data_dir, os.W_OK)
    return {
        "name": "Data directory",
        "status": CheckStatus.OK if writable else CheckStatus.ERROR,
        "details": "writable" if writable else "not writable",
    }


def _check_database() -> dict[str, Any]:
    """Check database connectivity."""
    from cleveragents.config.settings import get_settings

    settings = get_settings()
    db_url = settings.database_url

    if db_url.startswith("sqlite"):
        db_path_str = db_url.replace("sqlite:///", "")
        db_path = Path(db_path_str)
        if db_path.exists():
            writable = os.access(db_path, os.W_OK)
            return {
                "name": "Database",
                "status": CheckStatus.OK if writable else CheckStatus.ERROR,
                "details": "writable" if writable else "locked or not writable",
            }
        # DB file doesn't exist yet — that's OK for SQLite (created on first use)
        parent = db_path.parent
        if parent.exists() and os.access(parent, os.W_OK):
            return {
                "name": "Database",
                "status": CheckStatus.OK,
                "details": "will be created on first use",
            }
        return {
            "name": "Database",
            "status": CheckStatus.ERROR,
            "details": f"parent dir not writable ({parent})",
        }

    return {
        "name": "Database",
        "status": CheckStatus.OK,
        "details": "configured",
    }


def _check_providers() -> list[dict[str, Any]]:
    """Check provider API key configuration."""
    from cleveragents.config.settings import get_settings

    settings = get_settings()
    results: list[dict[str, Any]] = []

    provider_checks = [
        ("openai", "OPENAI_API_KEY"),
        ("anthropic", "ANTHROPIC_API_KEY"),
        ("google", "GOOGLE_API_KEY"),
        ("azure", "AZURE_OPENAI_API_KEY"),
        ("openrouter", "OPENROUTER_API_KEY"),
        ("gemini", "GEMINI_API_KEY"),
        ("cohere", "COHERE_API_KEY"),
        ("groq", "GROQ_API_KEY"),
        ("together", "TOGETHER_API_KEY"),
    ]

    for provider_name, env_var in provider_checks:
        configured = settings.has_provider_configured(provider_name)
        results.append(
            {
                "name": f"{provider_name.capitalize()} key",
                "status": CheckStatus.OK if configured else CheckStatus.WARN,
                "details": "configured" if configured else "missing",
                "recommendation": (
                    f"Set {env_var} to enable {provider_name.capitalize()} models"
                    if not configured
                    else None
                ),
            }
        )

    return results


def _check_disk_space() -> dict[str, Any]:
    """Check available disk space."""
    try:
        usage = shutil.disk_usage(Path.cwd())
        free_gb = usage.free / (1024**3)
        if free_gb < 0.5:
            return {
                "name": "Disk space",
                "status": CheckStatus.ERROR,
                "details": f"{free_gb:.1f} GB free (critically low)",
            }
        if free_gb < 1.0:
            return {
                "name": "Disk space",
                "status": CheckStatus.WARN,
                "details": f"{free_gb:.1f} GB free (low)",
            }
        return {
            "name": "Disk space",
            "status": CheckStatus.OK,
            "details": f"{free_gb:.1f} GB free",
        }
    except OSError:
        return {
            "name": "Disk space",
            "status": CheckStatus.WARN,
            "details": "unable to check",
        }


def _check_git() -> dict[str, Any]:
    """Check git availability."""
    try:
        result = subprocess.run(
            ["git", "--version"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if result.returncode == 0:
            version_str = result.stdout.strip().replace("git version ", "")
            return {
                "name": "Git",
                "status": CheckStatus.OK,
                "details": f"git {version_str}",
            }
    except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
        pass
    return {
        "name": "Git",
        "status": CheckStatus.ERROR,
        "details": "not found",
        "recommendation": "Install git for version control integration",
    }


def _check_file_permissions() -> dict[str, Any]:
    """Check file permissions on the data directory."""
    from cleveragents.config.settings import get_settings

    settings = get_settings()
    data_dir = settings.data_dir

    if not data_dir.exists():
        return {
            "name": "File permissions",
            "status": CheckStatus.WARN,
            "details": "data dir does not exist",
        }

    readable = os.access(data_dir, os.R_OK)
    writable = os.access(data_dir, os.W_OK)
    if readable and writable:
        return {
            "name": "File permissions",
            "status": CheckStatus.OK,
            "details": "data dir r/w",
        }
    perms = []
    if readable:
        perms.append("r")
    if writable:
        perms.append("w")
    return {
        "name": "File permissions",
        "status": CheckStatus.ERROR if not writable else CheckStatus.WARN,
        "details": f"data dir {''.join(perms) or 'no access'}",
    }


def _check_stale_locks() -> dict[str, Any]:
    """Check for stale (expired) concurrency locks."""
    try:
        from sqlalchemy.orm import Session, sessionmaker

        db_url = get_database_url()
        engine = create_engine(db_url, echo=False)

        # Only check if the locks table exists
        inspector = sa_inspect(engine)
        if "locks" not in inspector.get_table_names():
            return {
                "name": "Stale locks",
                "status": CheckStatus.OK,
                "details": "locks table not yet created",
            }

        factory: sessionmaker[Session] = sessionmaker(
            bind=engine, expire_on_commit=False
        )
        svc = LockService(session_factory=factory)
        count = svc.count_stale_locks()
        if count == 0:
            return {
                "name": "Stale locks",
                "status": CheckStatus.OK,
                "details": "0 stale locks",
            }
        return {
            "name": "Stale locks",
            "status": CheckStatus.WARN,
            "details": f"{count} stale lock(s) found",
            "recommendation": "Run lock cleanup or restart the service",
        }
    except Exception:
        return {
            "name": "Stale locks",
            "status": CheckStatus.WARN,
            "details": "unable to check",
        }


def _check_async_worker_health() -> dict[str, Any]:
    """Check async worker configuration and health."""
    try:
        from cleveragents.config.settings import get_settings

        settings = get_settings()
        enabled = settings.async_enabled
        if not enabled:
            return {
                "name": "Async workers",
                "status": CheckStatus.OK,
                "details": "disabled (async.enabled=false)",
            }
        return {
            "name": "Async workers",
            "status": CheckStatus.OK,
            "details": (
                f"enabled, max_workers={settings.async_max_workers}, "
                f"poll_interval={settings.async_poll_interval}s"
            ),
        }
    except Exception:
        return {
            "name": "Async workers",
            "status": CheckStatus.WARN,
            "details": "unable to check",
        }


def _check_error_patterns() -> dict[str, Any]:
    """Check the Error Pattern Database status."""
    try:
        from cleveragents.application.services.error_pattern_service import (
            ErrorPatternService,
        )

        service = ErrorPatternService()
        stats = service.get_statistics()
        total = stats["total_patterns"]
        return {
            "name": "Error Pattern DB",
            "status": CheckStatus.OK,
            "details": (
                f"{total} patterns, {stats['total_occurrences']} total occurrences"
            ),
        }
    except Exception:
        return {
            "name": "Error Pattern DB",
            "status": CheckStatus.OK,
            "details": "empty (no patterns recorded)",
        }


def build_diagnostics_data() -> dict[str, Any]:
    """Run all diagnostic checks and return structured results."""
    start = time.monotonic()

    checks: list[dict[str, Any]] = []
    checks.append(_check_config_file())
    checks.append(_check_data_dir())
    checks.append(_check_database())
    checks.extend(_check_providers())
    checks.append(_check_disk_space())
    checks.append(_check_file_permissions())
    checks.append(_check_git())
    checks.append(_check_stale_locks())

    checks.append(_check_async_worker_health())
    checks.append(_check_error_patterns())

    elapsed = time.monotonic() - start

    total = len(checks)
    ok_count = sum(1 for c in checks if c["status"] == CheckStatus.OK)
    warn_count = sum(1 for c in checks if c["status"] == CheckStatus.WARN)
    error_count = sum(1 for c in checks if c["status"] == CheckStatus.ERROR)

    recommendations: list[str] = []
    for check in checks:
        rec = check.get("recommendation")
        if rec:
            recommendations.append(rec)

    return {
        "checks": checks,
        "summary": {
            "total": total,
            "ok": ok_count,
            "warnings": warn_count,
            "errors": error_count,
            "duration_s": round(elapsed, 2),
        },
        "recommendations": recommendations,
        "has_errors": error_count > 0,
        "has_warnings": warn_count > 0,
    }


# ---------------------------------------------------------------------------
# Rich rendering helpers
# ---------------------------------------------------------------------------


def render_version_rich(data: dict[str, Any]) -> None:
    """Print version information using Rich panels."""
    from rich.panel import Panel

    from cleveragents.cli.main import get_console

    console = get_console()

    # CLI Version panel
    version_lines = [
        "[bold]CleverAgents CLI[/bold]",
        f"[blue]Version:[/blue] {data['version']}",
        f"[blue]Channel:[/blue] {data['channel']}",
        f"[blue]Python:[/blue] {data['python']}",
    ]
    console.print(Panel("\n".join(version_lines), title="CLI Version", expand=False))

    # Build panel
    build_lines = [
        f"[green]Build Date:[/green] {data['build_date']}",
        f"[magenta]Commit:[/magenta] {data['commit']}",
        f"[blue]Schema:[/blue] {data['schema']}",
        f"[blue]Platform:[/blue] {data['platform']}",
    ]
    console.print(Panel("\n".join(build_lines), title="Build", expand=False))

    # Dependencies panel
    deps = data.get("dependencies", {})
    dep_lines = [f"[blue]{k}:[/blue] {v}" for k, v in deps.items()]
    if dep_lines:
        console.print(Panel("\n".join(dep_lines), title="Dependencies", expand=False))

    console.print("[green]OK[/green] Version reported")


def render_info_rich(data: dict[str, Any]) -> None:
    """Print info using Rich panels."""
    from rich.panel import Panel

    from cleveragents.cli.main import get_console

    console = get_console()

    env_lines = [
        f"[blue]Data Dir:[/blue] {data['data_dir']}",
        f"[blue]Config:[/blue] {data['config_path']}",
        f"[green]Database:[/green] {data['database']}",
        f"[yellow]Server Mode:[/yellow] {data['server_mode']}",
        f"[blue]Platform:[/blue] {data['platform']}",
    ]
    console.print(Panel("\n".join(env_lines), title="Environment", expand=False))

    runtime_lines = [
        f"[magenta]Automation:[/magenta] {data['automation']}",
        f"[blue]Providers:[/blue] {data['providers_configured']} configured",
        f"[blue]Debug Mode:[/blue] {data['debug_mode']}",
    ]
    console.print(Panel("\n".join(runtime_lines), title="Runtime", expand=False))

    storage = data.get("storage", {})
    if storage:
        storage_lines = [f"[blue]{k}:[/blue] {v}" for k, v in storage.items()]
        console.print(Panel("\n".join(storage_lines), title="Storage", expand=False))

    console.print("[green]OK[/green] Environment details ready")


def render_diagnostics_rich(data: dict[str, Any]) -> None:
    """Print diagnostics using Rich panels and table."""
    from rich.panel import Panel
    from rich.table import Table

    from cleveragents.cli.main import get_console

    console = get_console()

    # Checks table
    table = Table(title="Checks", show_header=True, expand=False)
    table.add_column("Check", style="cyan")
    table.add_column("Status")
    table.add_column("Details")

    for check in data["checks"]:
        status = check["status"]
        if status == CheckStatus.OK:
            status_text = "[green]OK[/green]"
        elif status == CheckStatus.WARN:
            status_text = "[yellow]WARN[/yellow]"
        else:
            status_text = "[red]ERROR[/red]"
        table.add_row(check["name"], status_text, check.get("details", ""))

    console.print(table)

    # Summary panel
    summary = data["summary"]
    summary_lines = [
        f"[blue]Checks:[/blue] {summary['total']} total",
        f"[yellow]Warnings:[/yellow] {summary['warnings']}",
        f"[red]Errors:[/red] {summary['errors']}",
        f"[green]Duration:[/green] {summary['duration_s']}s",
    ]
    console.print(Panel("\n".join(summary_lines), title="Summary", expand=False))

    # Recommendations
    recs = data.get("recommendations", [])
    if recs:
        rec_lines = [f"- {r}" for r in recs]
        console.print(
            Panel("\n".join(rec_lines), title="Recommendations", expand=False)
        )

    # Final status line
    if data["has_errors"]:
        console.print(f"[red]ERROR[/red] {summary['errors']} errors must be resolved")
    elif data["has_warnings"]:
        console.print(
            f"[yellow]WARN[/yellow] {summary['warnings']} warnings require attention"
        )
    else:
        console.print("[green]OK[/green] All checks passed")
