"""Health check orchestrator for heartbeat monitoring."""

from __future__ import annotations

import logging
import subprocess
import time
from pathlib import Path

from heartbeat.checks.checksum import check_checksum
from heartbeat.checks.clickhouse import (
    collect_clickhouse_health_metrics,
    collect_clickhouse_stats,
)
from heartbeat.checks.junction import collect_junction_telemetry
from heartbeat.checks.kintsugi import parse_kintsugi_ndjson
from heartbeat.checks.ratelimit import check_ratelimit
from heartbeat.checks.resources import collect_resource_peaks
from heartbeat.checks.seeder import check_seeder
from heartbeat.checks.services import (
    check_oom_kills,
    check_systemd_overrides,
    collect_service_stats,
    is_kintsugi_catchup_active,
)
from heartbeat.checks.sidecar import (
    check_ch_writer_metrics,
    check_dead_letters,
    check_ws_errors,
)
from heartbeat.checks.stathera import collect_stathera
from heartbeat.checks.version import check_version_drift
from heartbeat.checks.vision_oracle import check_vision_oracle
from heartbeat.checks.yesterday import collect_yesterday_completion

logger = logging.getLogger("health-heartbeat")


def run_checks() -> dict[str, bool | str]:
    """Run all health checks. Returns dict of check_name -> pass/fail."""
    results: dict[str, bool | str] = {}

    # ClickHouse connectivity
    client = None
    try:
        import clickhouse_connect

        client = clickhouse_connect.get_client(host="localhost", port=8123)
        row = client.query("SELECT 1")
        results["clickhouse"] = row.result_rows[0][0] == 1
    except Exception:
        logger.warning("ClickHouse connectivity check failed", exc_info=True)
        results["clickhouse"] = False

    # Service status (daemons + catch-up)
    # kintsugi and kintsugi-catchup conflict — exactly one should be active.
    catchup_active = is_kintsugi_catchup_active()
    for svc in ("sidecar", "kintsugi"):
        name = f"opendeviationbar-{svc}"
        try:
            r = subprocess.run(
                ["systemctl", "--user", "is-active", name],
                capture_output=True,
                text=True,
                timeout=5,
                check=False,
            )
            active = r.returncode == 0
            # kintsugi is "healthy" if either kintsugi or kintsugi-catchup is active
            if svc == "kintsugi" and not active and catchup_active:
                active = True
            results[svc] = active
        except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
            results[svc] = False

    # Track which kintsugi mode is running for display
    if catchup_active:
        results["kintsugi_mode"] = "catch-up"
    elif results.get("kintsugi"):
        results["kintsugi_mode"] = "normal"
    else:
        results["kintsugi_mode"] = "stopped"

    # Disk space (>10 GB free)
    try:
        import shutil

        usage = shutil.disk_usage("/")
        free_gb = usage.free / (1024**3)
        results["disk"] = free_gb > 10.0
        results["disk_free_gb"] = f"{free_gb:.1f}"
        results["disk_used_pct"] = f"{usage.used / usage.total * 100:.0f}"
    except OSError:
        results["disk"] = False

    # OOM kill detection: flag services with recent OOM kills (Issue #185)
    for svc in ("sidecar", "kintsugi"):
        oom_count = check_oom_kills(svc)
        if oom_count > 0:
            results[f"{svc}_oom"] = oom_count
            results[svc] = False

    # Systemd override detection
    overrides = check_systemd_overrides()
    if overrides:
        results["systemd_overrides"] = ", ".join(overrides)

    # Service uptime and memory
    collect_service_stats(results)

    # Kintsugi telemetry: parse NDJSON events
    ndjson = Path.home() / "opendeviationbar-py" / "logs" / "kintsugi.ndjson"
    kintsugi_ndjson = parse_kintsugi_ndjson(ndjson)
    if kintsugi_ndjson["daemon_age_h"] is not None:
        results["kintsugi_telemetry"] = kintsugi_ndjson["daemon_age_h"] < 2.0
        results["kintsugi_telemetry_age"] = f"{kintsugi_ndjson['daemon_age_h']:.1f}h"
    elif ndjson.exists():
        age_hours = (time.time() - ndjson.stat().st_mtime) / 3600
        results["kintsugi_telemetry"] = age_hours < 2.0
        results["kintsugi_telemetry_age"] = f"{age_hours:.1f}h"
    else:
        results["kintsugi_telemetry"] = False

    # Stuck repair detection
    if kintsugi_ndjson["stuck_repair"]:
        results["kintsugi_stuck"] = False
        results["kintsugi_stuck_detail"] = kintsugi_ndjson["stuck_repair"]
    elif kintsugi_ndjson["daemon_age_h"] is not None:
        results["kintsugi_stuck"] = True

    # Active repair visibility
    if kintsugi_ndjson["active_repair"]:
        results["kintsugi_active_repair"] = kintsugi_ndjson["active_repair"]
    if kintsugi_ndjson["recent_healed"]:
        results["kintsugi_recent_healed"] = " | ".join(kintsugi_ndjson["recent_healed"])

    # Shard queue depth
    if kintsugi_ndjson["total_shards"] is not None:
        results["kintsugi_total_shards"] = str(kintsugi_ndjson["total_shards"])

    # Kintsugi 24h repair summary
    if client is not None:
        try:
            row = client.query(
                "SELECT countIf(result='healed'), countIf(result='clean'), "
                "countIf(result='failed'), "
                "countIf(result='failed' AND timestamp > now() - INTERVAL 1 HOUR) "
                "FROM opendeviationbar_cache.kintsugi_log "
                "WHERE timestamp > now() - INTERVAL 24 HOUR"
            )
            if not row.result_rows:
                logger.warning("kintsugi_log query returned empty result")
            else:
                healed, clean, failed, failed_recent = row.result_rows[0]
                results["kintsugi_healed_24h"] = healed
                results["kintsugi_clean_24h"] = clean
                results["kintsugi_failed_24h"] = failed
                results["kintsugi_repairs"] = f"{healed}h/{clean}c/{failed}f"  # legacy compact
                results["kintsugi_failures"] = failed_recent == 0
                row2 = client.query(
                    "SELECT sum(bars_written) "
                    "FROM opendeviationbar_cache.kintsugi_log "
                    "WHERE timestamp > now() - INTERVAL 24 HOUR"
                )
                if not row2.result_rows:
                    logger.warning("kintsugi bars_written query returned empty result")
                else:
                    bars_24h = row2.result_rows[0][0] or 0
                    results["kintsugi_bars_24h"] = f"{bars_24h:,}"
        except (OSError, RuntimeError, ConnectionError, ValueError):
            logger.warning("kintsugi repair query failed", exc_info=True)

    # ClickHouse database stats
    if client is not None:
        try:
            collect_clickhouse_stats(client, results)
        except (OSError, RuntimeError, ConnectionError, ValueError):
            logger.warning("clickhouse stats query failed", exc_info=True)

    # ClickHouse health metrics (pending mutations + active parts)
    if client is not None:
        try:
            collect_clickhouse_health_metrics(client, results)
        except (OSError, RuntimeError, ConnectionError, ValueError):
            logger.warning("clickhouse health metrics query failed", exc_info=True)

    # Stathera audit
    if client is not None:
        try:
            collect_stathera(client, results)
        except (OSError, RuntimeError, ConnectionError, ValueError):
            logger.debug("stathera audit failed", exc_info=True)

    # Yesterday orphan completion (TELEM-02)
    if client is not None:
        try:
            collect_yesterday_completion(client, results)
        except (OSError, RuntimeError, ConnectionError, ValueError):
            logger.debug("yesterday completion check failed", exc_info=True)

    # Kintsugi memory usage vs limit
    kintsugi_unit = (
        "opendeviationbar-kintsugi-catchup"
        if catchup_active
        else "opendeviationbar-kintsugi"
    )
    try:
        r = subprocess.run(
            [
                "systemctl",
                "--user",
                "show",
                kintsugi_unit,
                "--property=MemoryCurrent,MemoryHigh",
            ],
            capture_output=True,
            text=True,
            timeout=5,
            check=False,
        )
        props = dict(
            line.split("=", 1) for line in r.stdout.strip().split("\n") if "=" in line
        )
        current = int(props.get("MemoryCurrent", 0))
        high_str = props.get("MemoryHigh", "0")
        high = int(high_str) if high_str.isdigit() else 0
        if high > 0:
            pct = current / high * 100
            results["kintsugi_memory"] = pct < 95.0
            results["kintsugi_memory_pct"] = f"{pct:.0f}%"
    except (FileNotFoundError, subprocess.TimeoutExpired, OSError, ValueError):
        logger.debug("kintsugi memory check failed", exc_info=True)

    # Version drift
    check_version_drift(results)

    # Circuit breaker state
    try:
        from opendeviationbar.orchestration.open_deviation_bars_cache import (
            get_fatal_write_breaker,
        )

        state = get_fatal_write_breaker().state.value
        results["circuit_breaker"] = state
        results["circuit_breaker_healthy"] = state in ("closed", "n/a")
    except (ImportError, AttributeError):
        results["circuit_breaker"] = "n/a"

    # Resource peaks from Prometheus
    collect_resource_peaks(results)

    # Seeder state
    check_seeder(results)

    # Dead letter files (sidecar write failures)
    check_dead_letters(results)

    # WS error classification (429/ban = fatal, disconnect = transient)
    check_ws_errors(results)

    # Rust CH writer metrics (Issue #318, Phase 37)
    check_ch_writer_metrics(results)

    # REST API rate limit utilization (D-22, D-24)
    check_ratelimit(results)

    # Weekly Vision vs REST oracle check (D-10)
    try:
        check_vision_oracle(results)
    except (OSError, RuntimeError, ConnectionError, ValueError):
        logger.debug("vision oracle check failed", exc_info=True)

    # Checksum verification health (TELEM-01, TELEM-02)
    check_checksum(results)

    # Junction telemetry (TELEM-03) -- informational only, not health-gating
    try:
        collect_junction_telemetry(results)
    except (OSError, RuntimeError, ConnectionError, ValueError):
        logger.debug("junction telemetry check failed", exc_info=True)

    return results
