"""Prometheus metrics for the coordinator."""

from __future__ import annotations

import sqlite3
from datetime import UTC, datetime

import structlog

try:
    from prometheus_client import CONTENT_TYPE_LATEST, Counter, Gauge, Histogram, generate_latest

    METRICS_AVAILABLE = True
except ImportError:
    METRICS_AVAILABLE = False

logger = structlog.get_logger()

if METRICS_AVAILABLE:
    # Counters
    JOBS_SUBMITTED = Counter("hive_jobs_submitted_total", "Total jobs submitted")
    JOBS_COMPLETED = Counter("hive_jobs_completed_total", "Total jobs completed")
    JOBS_FAILED = Counter("hive_jobs_failed_total", "Total jobs permanently failed")
    SHARDS_CLAIMED = Counter("hive_shards_claimed_total", "Total shards claimed by workers")
    SHARDS_COMPLETED = Counter("hive_shards_completed_total", "Total shards completed")
    SHARDS_FAILED = Counter("hive_shards_failed_total", "Total shards that exhausted retries and failed")
    WEBHOOK_DELIVERIES = Counter("hive_webhook_deliveries_total", "Total webhook delivery attempts")
    WEBHOOK_FAILURES = Counter("hive_webhook_failures_total", "Total webhook delivery failures")
    HTTP_REQUESTS = Counter(
        "hive_http_requests_total",
        "Total HTTP requests handled by the coordinator",
        labelnames=("method", "status_class"),
    )
    DATABASE_ERRORS = Counter(
        "hive_database_errors_total",
        "Total SQLite errors surfaced by coordinator operations",
        labelnames=("operation",),
    )

    # Gauges (set periodically by the reaper or on-demand)
    PENDING_SHARDS = Gauge("hive_pending_shards", "Shards waiting to be claimed")
    ACTIVE_WORKERS = Gauge("hive_active_workers", "Workers with recent heartbeat")
    PENDING_WEBHOOKS = Gauge("hive_pending_webhooks", "Webhooks pending retry")
    QUEUED_JOBS = Gauge("hive_jobs_queued", "Jobs still waiting for workers")
    PROCESSING_JOBS = Gauge("hive_jobs_processing", "Jobs actively being processed by workers")
    STUCK_JOBS = Gauge("hive_jobs_stuck", "Jobs stuck in processing beyond the alert threshold")

    # Histograms
    SHARD_PROCESSING_DURATION = Histogram(
        "hive_shard_processing_duration_seconds",
        "Time from shard claim to completion",
        buckets=[5, 15, 30, 60, 120, 300, 600],
    )
    JOB_TURNAROUND_DURATION = Histogram(
        "hive_job_turnaround_seconds",
        "Time from job submission to terminal completion or failure",
        buckets=[5, 15, 30, 60, 120, 300, 600, 1800, 3600, 7200],
    )
    JOB_PROCESSING_DURATION = Histogram(
        "hive_job_processing_duration_seconds",
        "Time from job creation to final completion",
        buckets=[30, 60, 120, 180, 300, 600, 900, 1800, 3600],
    )


def increment_job_submitted() -> None:
    if METRICS_AVAILABLE:
        JOBS_SUBMITTED.inc()


def increment_job_completed() -> None:
    if METRICS_AVAILABLE:
        JOBS_COMPLETED.inc()


def increment_job_failed() -> None:
    if METRICS_AVAILABLE:
        JOBS_FAILED.inc()


def increment_shard_claimed() -> None:
    if METRICS_AVAILABLE:
        SHARDS_CLAIMED.inc()


def increment_shard_completed() -> None:
    if METRICS_AVAILABLE:
        SHARDS_COMPLETED.inc()


def increment_shard_failed() -> None:
    if METRICS_AVAILABLE:
        SHARDS_FAILED.inc()


def increment_webhook_delivery(*, success: bool) -> None:
    if not METRICS_AVAILABLE:
        return
    WEBHOOK_DELIVERIES.inc()
    if not success:
        WEBHOOK_FAILURES.inc()


def record_http_request(method: str, status_code: int) -> None:
    if not METRICS_AVAILABLE:
        return
    status_class = f"{status_code // 100}xx"
    HTTP_REQUESTS.labels(method=method.upper(), status_class=status_class).inc()


def increment_database_error(operation: str) -> None:
    if METRICS_AVAILABLE:
        DATABASE_ERRORS.labels(operation=operation).inc()


def observe_shard_processing_duration(claimed_at: str | None, completed_at: str | None) -> None:
    if not METRICS_AVAILABLE:
        return
    claimed = _parse_timestamp(claimed_at)
    completed = _parse_timestamp(completed_at)
    if not claimed or not completed or completed < claimed:
        return
    SHARD_PROCESSING_DURATION.observe((completed - claimed).total_seconds())


def observe_job_turnaround_duration(created_at: str | None, completed_at: str | None) -> None:
    if not METRICS_AVAILABLE:
        return
    created = _parse_timestamp(created_at)
    completed = _parse_timestamp(completed_at)
    if not created or not completed or completed < created:
        return
    JOB_TURNAROUND_DURATION.observe((completed - created).total_seconds())


def observe_job_processing_duration(created_at: str | None, completed_at: str | None) -> None:
    if not METRICS_AVAILABLE:
        return
    created = _parse_timestamp(created_at)
    completed = _parse_timestamp(completed_at)
    if not created or not completed or completed < created:
        return
    JOB_PROCESSING_DURATION.observe((completed - created).total_seconds())


def refresh_operational_gauges() -> None:
    if not METRICS_AVAILABLE:
        return

    from hive_compute.config import get_settings
    from hive_compute.coordinator.database import get_db_ctx

    stale_minutes = get_settings().stale_job_minutes
    with get_db_ctx() as db:
        pending_shards = db.execute(
            "SELECT COUNT(*) AS cnt FROM shards WHERE status = 'pending'",
        ).fetchone()["cnt"]
        active_workers = db.execute(
            """
            SELECT COUNT(*) AS cnt
            FROM workers
            WHERE last_heartbeat >= datetime('now', ?)
            """,
            (f"-{stale_minutes} minutes",),
        ).fetchone()["cnt"]
        pending_webhooks = db.execute(
            """
            SELECT COUNT(*) AS cnt
            FROM webhook_deliveries
            WHERE status IN ('pending', 'retrying')
            """,
        ).fetchone()["cnt"]
        queued_jobs = db.execute(
            """
            SELECT COUNT(*) AS cnt
            FROM jobs
            WHERE status = 'pending'
            """,
        ).fetchone()["cnt"]
        processing_jobs = db.execute(
            """
            SELECT COUNT(*) AS cnt
            FROM jobs
            WHERE status = 'processing'
            """,
        ).fetchone()["cnt"]
        stuck_jobs = db.execute(
            """
            SELECT COUNT(*) AS cnt
            FROM jobs
            WHERE status = 'processing'
              AND last_heartbeat < datetime('now', '-15 minutes')
            """,
        ).fetchone()["cnt"]

    PENDING_SHARDS.set(pending_shards)
    ACTIVE_WORKERS.set(active_workers)
    PENDING_WEBHOOKS.set(pending_webhooks)
    QUEUED_JOBS.set(queued_jobs)
    PROCESSING_JOBS.set(processing_jobs)
    STUCK_JOBS.set(stuck_jobs)


def _parse_timestamp(value: str | None) -> datetime | None:
    if not value:
        return None
    normalized = value.replace("Z", "+00:00")
    try:
        parsed = datetime.fromisoformat(normalized)
    except ValueError:
        return None
    if parsed.tzinfo is None:
        return parsed.replace(tzinfo=UTC)
    return parsed.astimezone(UTC)


def get_metrics_response() -> tuple[bytes, str]:
    """Generate Prometheus metrics output. Returns (body, content_type)."""
    if not METRICS_AVAILABLE:
        return b"# prometheus_client not installed\n", "text/plain"
    try:
        refresh_operational_gauges()
    except sqlite3.Error:
        increment_database_error("refresh_operational_gauges")
        logger.exception("Failed to refresh operational gauges")
    return generate_latest(), CONTENT_TYPE_LATEST
