"""
InsAIts SDK - Prometheus Metrics Collector
============================================
Lightweight metrics collection compatible with Prometheus
text exposition format (no external dependencies).

Provides three metric types:
    - Counter: Monotonically increasing values (messages, anomalies)
    - Gauge: Point-in-time values (active agents, queue depth)
    - Histogram: Distribution observations (processing latency)

Histogram buckets follow Prometheus conventions with
predefined latency thresholds.

Usage:
    metrics = MetricsCollector()
    metrics.increment("messages_total", {"tier": "free"})
    metrics.observe("processing_duration_ms", 42.5)
    print(metrics.render_prometheus())
"""

from collections import defaultdict
from typing import Any, Dict, List, Optional, Tuple


# Default histogram buckets for latency (milliseconds)
DEFAULT_LATENCY_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1000, 5000]

# Separator for label encoding
_LABEL_SEP = "|"


def _encode_labels(labels: Optional[Dict[str, str]] = None) -> str:
    """Encode label dict into a stable string key."""
    if not labels:
        return ""
    pairs = sorted(labels.items())
    return _LABEL_SEP.join(f"{k}={v}" for k, v in pairs)


def _format_labels(labels_key: str) -> str:
    """Convert encoded label key to Prometheus label format."""
    if not labels_key:
        return ""
    parts = labels_key.split(_LABEL_SEP)
    formatted = ", ".join(
        f'{k}="{v}"'
        for part in parts
        for k, v in [part.split("=", 1)]
    )
    return "{" + formatted + "}"


class MetricsCollector:
    """Prometheus-compatible metrics collector.

    Tracks counters, gauges, and histogram observations.
    Renders metrics in Prometheus text exposition format.
    """

    def __init__(self) -> None:
        """Initialize empty metrics store."""
        # {metric_name: {encoded_labels: value}}
        self._counters: Dict[str, Dict[str, float]] = defaultdict(
            lambda: defaultdict(float)
        )
        self._gauges: Dict[str, Dict[str, float]] = defaultdict(
            lambda: defaultdict(float)
        )
        # {metric_name: {encoded_labels: [observations]}}
        self._histograms: Dict[str, Dict[str, List[float]]] = defaultdict(
            lambda: defaultdict(list)
        )

    def increment(
        self,
        metric: str,
        labels: Optional[Dict[str, str]] = None,
        amount: float = 1.0,
    ) -> None:
        """Increment a counter metric.

        Args:
            metric: Metric name (e.g. "insaits_messages_total")
            labels: Optional label key-value pairs
            amount: Amount to increment (default 1)
        """
        key = _encode_labels(labels)
        self._counters[metric][key] += amount

    def gauge(
        self,
        metric: str,
        value: float,
        labels: Optional[Dict[str, str]] = None,
    ) -> None:
        """Set a gauge metric to a specific value.

        Args:
            metric: Metric name (e.g. "insaits_active_agents")
            value: Current value
            labels: Optional label key-value pairs
        """
        key = _encode_labels(labels)
        self._gauges[metric][key] = value

    def observe(
        self,
        metric: str,
        value: float,
        labels: Optional[Dict[str, str]] = None,
    ) -> None:
        """Record a histogram observation.

        Args:
            metric: Metric name (e.g. "insaits_processing_duration_ms")
            value: Observed value
            labels: Optional label key-value pairs
        """
        key = _encode_labels(labels)
        self._histograms[metric][key].append(value)

    def record_message(
        self,
        had_anomaly: bool,
        severity: str,
        processing_ms: float,
    ) -> None:
        """Convenience method to record a processed message.

        Args:
            had_anomaly: Whether the message had anomalies
            severity: Highest severity level
            processing_ms: Processing duration in milliseconds
        """
        self.increment("insaits_messages_total")
        if had_anomaly:
            self.increment(
                "insaits_anomalies_total",
                {"severity": severity},
            )
        self.observe("insaits_processing_duration_ms", processing_ms)

    def render_prometheus(self) -> str:
        """Render all metrics in Prometheus text exposition format.

        Returns:
            Multi-line string in Prometheus format
        """
        lines: List[str] = []

        # Counters
        for metric, label_values in sorted(self._counters.items()):
            lines.append(f"# TYPE {metric} counter")
            for labels_key, value in sorted(label_values.items()):
                labels_str = _format_labels(labels_key)
                lines.append(f"{metric}{labels_str} {value}")

        # Gauges
        for metric, label_values in sorted(self._gauges.items()):
            lines.append(f"# TYPE {metric} gauge")
            for labels_key, value in sorted(label_values.items()):
                labels_str = _format_labels(labels_key)
                lines.append(f"{metric}{labels_str} {value}")

        # Histograms
        for metric, label_values in sorted(self._histograms.items()):
            lines.append(f"# TYPE {metric} histogram")
            for labels_key, observations in sorted(label_values.items()):
                labels_str = _format_labels(labels_key)
                total = sum(observations)
                count = len(observations)

                for bucket in DEFAULT_LATENCY_BUCKETS:
                    bucket_count = sum(
                        1 for v in observations if v <= bucket
                    )
                    if labels_str:
                        inner = labels_str[1:-1]
                        bucket_labels = '{' + inner + f', le="{bucket}"' + '}'
                    else:
                        bucket_labels = '{' + f'le="{bucket}"' + '}'
                    lines.append(
                        f"{metric}_bucket{bucket_labels} {bucket_count}"
                    )

                inf_labels = (
                    '{' + labels_str[1:-1] + ', le="+Inf"}'
                    if labels_str
                    else '{le="+Inf"}'
                )
                lines.append(f"{metric}_bucket{inf_labels} {count}")
                lines.append(f"{metric}_sum{labels_str} {total}")
                lines.append(f"{metric}_count{labels_str} {count}")

        return "\n".join(lines) + "\n" if lines else ""

    def get_counter(
        self,
        metric: str,
        labels: Optional[Dict[str, str]] = None,
    ) -> float:
        """Get current counter value.

        Args:
            metric: Metric name
            labels: Optional labels

        Returns:
            Current counter value (0.0 if not set)
        """
        key = _encode_labels(labels)
        return self._counters.get(metric, {}).get(key, 0.0)

    def get_gauge_value(
        self,
        metric: str,
        labels: Optional[Dict[str, str]] = None,
    ) -> float:
        """Get current gauge value.

        Args:
            metric: Metric name
            labels: Optional labels

        Returns:
            Current gauge value (0.0 if not set)
        """
        key = _encode_labels(labels)
        return self._gauges.get(metric, {}).get(key, 0.0)

    def reset(self) -> None:
        """Reset all metrics to zero."""
        self._counters.clear()
        self._gauges.clear()
        self._histograms.clear()
