"""In-process metrics collection with Prometheus text format output."""

from __future__ import annotations

import threading
from typing import ClassVar


class Metrics:
    """Thread-safe metrics singleton with counters, gauges, and histograms."""

    _instance: ClassVar[Metrics | None] = None
    _lock: ClassVar[threading.Lock] = threading.Lock()

    def __new__(cls) -> Metrics:
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    inst = super().__new__(cls)
                    inst._counters: dict[str, float] = {}
                    inst._gauges: dict[str, float] = {}
                    inst._histograms: dict[str, list[float]] = {}
                    inst._data_lock = threading.Lock()
                    cls._instance = inst
        return cls._instance

    def inc(self, name: str, value: float = 1.0, labels: dict[str, str] | None = None) -> None:
        """Increment a counter."""
        key = self._key(name, labels)
        with self._data_lock:
            self._counters[key] = self._counters.get(key, 0.0) + value

    def set_gauge(self, name: str, value: float, labels: dict[str, str] | None = None) -> None:
        """Set a gauge value."""
        key = self._key(name, labels)
        with self._data_lock:
            self._gauges[key] = value

    def observe(self, name: str, value: float, labels: dict[str, str] | None = None) -> None:
        """Record an observation for a histogram."""
        key = self._key(name, labels)
        with self._data_lock:
            self._histograms.setdefault(key, []).append(value)

    def get_counter(self, name: str, labels: dict[str, str] | None = None) -> float:
        """Get current counter value."""
        key = self._key(name, labels)
        with self._data_lock:
            return self._counters.get(key, 0.0)

    def get_gauge(self, name: str, labels: dict[str, str] | None = None) -> float:
        """Get current gauge value."""
        key = self._key(name, labels)
        with self._data_lock:
            return self._gauges.get(key, 0.0)

    def get_histogram(self, name: str, labels: dict[str, str] | None = None) -> list[float]:
        """Get histogram observations."""
        key = self._key(name, labels)
        with self._data_lock:
            return list(self._histograms.get(key, []))

    def _key(self, name: str, labels: dict[str, str] | None = None) -> str:
        if not labels:
            return name
        label_str = ",".join(f'{k}="{v}"' for k, v in sorted(labels.items()))
        return f"{name}{{{label_str}}}"

    def reset(self) -> None:
        """Reset all metrics (for testing)."""
        with self._data_lock:
            self._counters.clear()
            self._gauges.clear()
            self._histograms.clear()


# Standard histogram buckets
_DURATION_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]


def metrics_handler() -> str:
    """Return all metrics in Prometheus text exposition format."""
    m = Metrics()
    lines: list[str] = []

    with m._data_lock:
        # Counters
        for key, value in sorted(m._counters.items()):
            lines.append(f"{key} {value}")

        # Gauges
        for key, value in sorted(m._gauges.items()):
            lines.append(f"{key} {value}")

        # Histograms
        for key, observations in sorted(m._histograms.items()):
            if not observations:
                continue
            total = sum(observations)
            count = len(observations)
            for bucket in _DURATION_BUCKETS:
                bucket_count = sum(1 for o in observations if o <= bucket)
                lines.append(f'{key}_bucket{{le="{bucket}"}} {bucket_count}')
            lines.append(f'{key}_bucket{{le="+Inf"}} {count}')
            lines.append(f"{key}_sum {total}")
            lines.append(f"{key}_count {count}")

    lines.append("")  # trailing newline
    return "\n".join(lines)
