from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict

from entropic_core.core.runtime_events import RuntimeEvent


def _require_prom() -> None:
    try:
        import prometheus_client  # noqa: F401
    except Exception as e:  # pragma: no cover
        raise ImportError(
            "Prometheus extras not installed. Install with: pip install \"entropic-core[prometheus]\""
        ) from e


@dataclass
class PrometheusEventSink:
    """Export RuntimeEvents to Prometheus metrics.

    This sink is intentionally simple and safe:
    - bounded label cardinality (provider + model, but you can disable model label)
    - best-effort updates
    """

    namespace: str = "entropic"
    include_model_label: bool = True
    registry: Any = None

    def __post_init__(self) -> None:
        _require_prom()
        from prometheus_client import REGISTRY, Counter, Gauge, Histogram

        if self.registry is None:
            self.registry = REGISTRY

        labels = ["provider"] + (["model"] if self.include_model_label else [])

        def _lbls(event: RuntimeEvent) -> Dict[str, str]:
            d = {"provider": str(event.provider or "")}
            if self.include_model_label:
                d["model"] = str(event.model or "")
            return d

        self._labels = _lbls

        self.requests_total = Counter(
            "requests_total",
            "Total LLM requests observed",
            labelnames=labels,
            namespace=self.namespace,
            registry=self.registry,
        )
        self.responses_total = Counter(
            "responses_total",
            "Total LLM responses observed",
            labelnames=labels,
            namespace=self.namespace,
            registry=self.registry,
        )
        self.errors_total = Counter(
            "errors_total",
            "Total LLM call errors observed",
            labelnames=labels,
            namespace=self.namespace,
            registry=self.registry,
        )
        self.latency_ms = Histogram(
            "latency_ms",
            "LLM end-to-end latency in milliseconds",
            labelnames=labels,
            namespace=self.namespace,
            registry=self.registry,
            buckets=(5, 10, 25, 50, 100, 200, 500, 1000, 2500, 5000, 10000),
        )
        self.tokens_total = Counter(
            "tokens_total",
            "Total tokens observed",
            labelnames=labels + ["kind"],
            namespace=self.namespace,
            registry=self.registry,
        )
        self.entropy = Gauge(
            "entropy",
            "Entropy values observed",
            labelnames=labels + ["stage"],
            namespace=self.namespace,
            registry=self.registry,
        )
        self.interventions_total = Counter(
            "interventions_total",
            "Total interventions applied",
            labelnames=labels + ["kind"],
            namespace=self.namespace,
            registry=self.registry,
        )

    def emit(self, event: RuntimeEvent) -> None:
        try:
            et = str(event.type or "")
            lbl = self._labels(event)

            if et == "request":
                self.requests_total.labels(**lbl).inc()
                p = event.payload or {}
                eb = p.get("entropy_before")
                if eb is not None:
                    self.entropy.labels(**lbl, stage="before").set(float(eb))
                return

            if et == "error":
                self.errors_total.labels(**lbl).inc()
                return

            if et == "response":
                self.responses_total.labels(**lbl).inc()
                p = event.payload or {}

                lat = p.get("latency_ms")
                if lat is not None:
                    self.latency_ms.labels(**lbl).observe(float(lat))

                ea = p.get("entropy_after")
                if ea is not None:
                    self.entropy.labels(**lbl, stage="after").set(float(ea))

                usage = p.get("usage")
                if isinstance(usage, dict):
                    self.tokens_total.labels(**lbl, kind="prompt").inc(
                        float(usage.get("prompt_tokens", 0) or 0)
                    )
                    self.tokens_total.labels(**lbl, kind="completion").inc(
                        float(usage.get("completion_tokens", 0) or 0)
                    )
                    self.tokens_total.labels(**lbl, kind="total").inc(
                        float(usage.get("total_tokens", 0) or 0)
                    )

                interventions = p.get("interventions")
                if isinstance(interventions, list):
                    for k in interventions[:64]:
                        self.interventions_total.labels(**lbl, kind=str(k)).inc()
                return
        except Exception:
            return

    def start_http_server(self, port: int = 8000, addr: str = "0.0.0.0") -> None:
        _require_prom()
        from prometheus_client import start_http_server

        start_http_server(port, addr=addr, registry=self.registry)
