"""Self-diagnostic checks — census doctor.

Runs a series of health checks to verify that Census is properly
configured and can communicate with the CertiSigma API.  Each check
reports OK / WARN / FAIL with an actionable message.

Checks:
  1. Python version and Census version
  2. Required/optional dependencies
  3. Config files (user, project)
  4. API connectivity (health endpoint, no key needed)
  5. API key validity (if configured)
  6. inotify watch limit (Linux only)
  7. Manifest health (if path provided)
"""

from __future__ import annotations

import platform
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

from . import __version__

# Shorter than production client default so `census doctor` fails fast on bad networks.
DOCTOR_HTTP_TIMEOUT_SEC = 8.0

STATUS_OK = "ok"
STATUS_WARN = "warn"
STATUS_FAIL = "fail"


@dataclass
class CheckResult:
    name: str
    status: str
    message: str
    detail: Optional[str] = None


@dataclass
class DoctorReport:
    checks: list[CheckResult] = field(default_factory=list)

    @property
    def ok_count(self) -> int:
        return sum(1 for c in self.checks if c.status == STATUS_OK)

    @property
    def warn_count(self) -> int:
        return sum(1 for c in self.checks if c.status == STATUS_WARN)

    @property
    def fail_count(self) -> int:
        return sum(1 for c in self.checks if c.status == STATUS_FAIL)

    @property
    def all_ok(self) -> bool:
        return self.fail_count == 0


def check_python_version() -> CheckResult:
    """Check Python version meets minimum (3.10)."""
    v = sys.version_info
    version_str = f"{v.major}.{v.minor}.{v.micro}"
    if v >= (3, 10):
        return CheckResult("python_version", STATUS_OK, f"Python {version_str}")
    return CheckResult("python_version", STATUS_FAIL, f"Python {version_str} (requires >= 3.10)")


def check_census_version() -> CheckResult:
    return CheckResult("census_version", STATUS_OK, f"certisigma-census {__version__}")


def check_sdk_version() -> CheckResult:
    """Check CertiSigma SDK is installed and report version."""
    try:
        import certisigma
        version = getattr(certisigma, "__version__", "unknown")
        return CheckResult("sdk_version", STATUS_OK, f"certisigma {version}")
    except ImportError:
        return CheckResult("sdk_version", STATUS_FAIL, "certisigma SDK not installed")


def check_optional_deps() -> list[CheckResult]:
    """Check optional dependencies."""
    results = []

    try:
        import watchdog
        v = getattr(watchdog, "__version__", getattr(watchdog, "version_string", "unknown"))
        results.append(CheckResult("watchdog", STATUS_OK, f"watchdog {v} (watch mode available)"))
    except ImportError:
        results.append(CheckResult("watchdog", STATUS_WARN, "watchdog not installed (install with: pip install certisigma-census[watch])"))

    try:
        import fpdf
        v = getattr(fpdf, "__version__", "unknown")
        results.append(CheckResult("fpdf2", STATUS_OK, f"fpdf2 {v} (PDF reports available)"))
    except ImportError:
        results.append(CheckResult("fpdf2", STATUS_WARN, "fpdf2 not installed (install with: pip install certisigma-census[report])"))

    return results


def check_config() -> list[CheckResult]:
    """Check config file status."""
    from .config import config_paths

    results = []
    paths = config_paths()

    user_path = paths.get("user")
    if user_path and user_path.exists():
        results.append(CheckResult("config_user", STATUS_OK, f"User config: {user_path}"))
    else:
        results.append(CheckResult("config_user", STATUS_WARN, "No user config (create with: census config init)"))

    project_path = paths.get("project")
    if project_path and project_path.exists():
        results.append(CheckResult("config_project", STATUS_OK, f"Project config: {project_path}"))
    else:
        results.append(CheckResult("config_project", STATUS_WARN, "No project config (create with: census config init --project)"))

    return results


def check_api_connectivity(base_url: str | None = None) -> CheckResult:
    """Check API connectivity via the public health endpoint."""
    try:
        from certisigma import CertiSigmaClient
    except ImportError:
        return CheckResult("api_health", STATUS_FAIL, "Cannot check — certisigma SDK not installed")

    kwargs: dict = {"timeout": DOCTOR_HTTP_TIMEOUT_SEC}
    if base_url:
        kwargs["base_url"] = base_url
    try:
        client = CertiSigmaClient(**kwargs)
    except Exception as exc:
        return CheckResult("api_health", STATUS_FAIL, f"API unreachable: {exc}")

    try:
        try:
            health = client.health()
            status_val = health.get("status", "unknown")
            return CheckResult("api_health", STATUS_OK, f"API reachable (status: {status_val})")
        except Exception as exc:
            return CheckResult("api_health", STATUS_FAIL, f"API unreachable: {exc}")
    finally:
        client.close()


def check_api_key(api_key: str | None = None) -> CheckResult:
    """Validate API key format and report configuration status.

    Note: full key validation requires an authenticated endpoint call,
    which may have side effects.  This check verifies the key is
    configured and has the expected format (starts with 'cs_').
    """
    import os

    from .config import load_config, mask_secret_tail

    cfg = load_config()
    key = api_key or os.environ.get("CERTISIGMA_API_KEY") or cfg.get("api_key") or ""
    if not key:
        return CheckResult("api_key", STATUS_WARN, "No API key configured (set CERTISIGMA_API_KEY or add to config)")

    masked = mask_secret_tail(key)

    if not key.startswith("cs_"):
        return CheckResult("api_key", STATUS_WARN, f"API key has unexpected format ({masked}) — expected prefix 'cs_'")

    return CheckResult("api_key", STATUS_OK, f"API key configured ({masked})")


def check_inotify() -> Optional[CheckResult]:
    """Check inotify watch limit (Linux only)."""
    if platform.system() != "Linux":
        return None

    try:
        limit = int(Path("/proc/sys/fs/inotify/max_user_watches").read_text().strip())
        if limit >= 524288:
            return CheckResult("inotify", STATUS_OK, f"inotify limit: {limit:,} (sufficient)")
        elif limit >= 65536:
            return CheckResult("inotify", STATUS_WARN, f"inotify limit: {limit:,} (may be low for large directories)")
        else:
            return CheckResult(
                "inotify", STATUS_WARN,
                f"inotify limit: {limit:,} (too low)",
                detail="Increase with: sudo sysctl -w fs.inotify.max_user_watches=524288",
            )
    except OSError:
        return CheckResult("inotify", STATUS_WARN, "Cannot read inotify limit")


def check_manifest(manifest_path: Path) -> CheckResult:
    """Check manifest file health (SQLite integrity check).

    Detects encrypted manifests and reports encryption status.
    """
    import sqlite3

    if not manifest_path.exists():
        enc_candidates = [
            manifest_path.with_suffix(manifest_path.suffix + ".enc"),
        ]
        for enc in enc_candidates:
            if enc.exists():
                from .encryption import is_encrypted_file
                if is_encrypted_file(enc):
                    return CheckResult(
                        "manifest", STATUS_OK,
                        f"Manifest encrypted at rest ({enc.name}). "
                        "Provide --encryption-key to inspect contents.",
                    )
        return CheckResult("manifest", STATUS_FAIL, f"Manifest not found: {manifest_path}")

    from .encryption import is_encrypted_file
    if is_encrypted_file(manifest_path):
        return CheckResult(
            "manifest", STATUS_OK,
            f"Manifest encrypted at rest ({manifest_path.name}). "
            "Provide --encryption-key to inspect contents.",
        )

    try:
        conn = sqlite3.connect(str(manifest_path))
        try:
            result = conn.execute("PRAGMA integrity_check").fetchone()
        finally:
            conn.close()
        if result and result[0] == "ok":
            from .manifest import Manifest
            with Manifest.load(manifest_path) as m:
                total = m.total
                attested = m.attested_count
            return CheckResult(
                "manifest", STATUS_OK,
                f"Manifest OK: {total} files, {attested} attested ({manifest_path.name})",
            )
        else:
            msg = result[0] if result else "unknown error"
            return CheckResult("manifest", STATUS_FAIL, f"Manifest corrupt: {msg}")
    except Exception as exc:
        return CheckResult("manifest", STATUS_FAIL, f"Cannot read manifest: {exc}")


def run_doctor(
    *,
    api_key: str | None = None,
    base_url: str | None = None,
    manifest_path: Path | None = None,
) -> DoctorReport:
    """Run all diagnostic checks and return a structured report."""
    report = DoctorReport()

    report.checks.append(check_python_version())
    report.checks.append(check_census_version())
    report.checks.append(check_sdk_version())
    report.checks.extend(check_optional_deps())
    report.checks.extend(check_config())
    report.checks.append(check_api_connectivity(base_url))
    report.checks.append(check_api_key(api_key))

    inotify = check_inotify()
    if inotify:
        report.checks.append(inotify)

    if manifest_path:
        report.checks.append(check_manifest(manifest_path))

    return report
