import time
from collections import defaultdict
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Optional


class MetricType(Enum):
    COUNTER = "counter"
    GAUGE = "gauge"
    HISTOGRAM = "histogram"
    TIMER = "timer"


@dataclass
class Metric:
    name: str
    type: MetricType
    value: float
    timestamp: datetime = field(default_factory=datetime.now)
    labels: dict[str, str] = field(default_factory=dict)


@dataclass
class TimerContext:
    metric_name: str
    labels: dict[str, str]
    collector: "MetricsCollector"
    start_time: float = field(default_factory=time.perf_counter)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        elapsed = time.perf_counter() - self.start_time
        self.collector.record_timer(self.metric_name, elapsed, labels=self.labels)
        return False


class MetricsCollector:
    def __init__(self):
        self._metrics: list[Metric] = []
        self._counters: dict[str, float] = defaultdict(float)
        self._gauges: dict[str, float] = {}
        self._histograms: dict[str, list[float]] = defaultdict(list)
        self._timers: dict[str, list[float]] = defaultdict(list)

    def increment(self, name: str, value: float = 1.0, labels: Optional[dict[str, str]] = None) -> None:
        key = self._make_key(name, labels)
        self._counters[key] += value
        self._record_metric(name, MetricType.COUNTER, self._counters[key], labels)

    def set_gauge(self, name: str, value: float, labels: Optional[dict[str, str]] = None) -> None:
        key = self._make_key(name, labels)
        self._gauges[key] = value
        self._record_metric(name, MetricType.GAUGE, value, labels)

    def record_histogram(self, name: str, value: float, labels: Optional[dict[str, str]] = None) -> None:
        key = self._make_key(name, labels)
        self._histograms[key].append(value)
        self._record_metric(name, MetricType.HISTOGRAM, value, labels)

    def record_timer(self, name: str, duration: float, labels: Optional[dict[str, str]] = None) -> None:
        key = self._make_key(name, labels)
        self._timers[key].append(duration)
        self._record_metric(name, MetricType.TIMER, duration, labels)

    def timer(self, name: str, labels: Optional[dict[str, str]] = None) -> TimerContext:
        return TimerContext(name, labels or {}, self)

    def _make_key(self, name: str, labels: Optional[dict[str, str]]) -> 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 _record_metric(
        self, name: str, metric_type: MetricType, value: float, labels: Optional[dict[str, str]]
    ) -> None:
        metric = Metric(
            name=name, type=metric_type, value=value, labels=labels or {}
        )
        self._metrics.append(metric)

    def get_counter(self, name: str, labels: Optional[dict[str, str]] = None) -> float:
        key = self._make_key(name, labels)
        return self._counters.get(key, 0.0)

    def get_gauge(self, name: str, labels: Optional[dict[str, str]] = None) -> Optional[float]:
        key = self._make_key(name, labels)
        return self._gauges.get(key)

    def get_histogram_stats(self, name: str, labels: Optional[dict[str, str]] = None) -> dict[str, float]:
        key = self._make_key(name, labels)
        values = self._histograms.get(key, [])
        if not values:
            return {}

        sorted_values = sorted(values)
        return {
            "count": len(values),
            "sum": sum(values),
            "min": min(values),
            "max": max(values),
            "mean": sum(values) / len(values),
            "p50": self._percentile(sorted_values, 50),
            "p95": self._percentile(sorted_values, 95),
            "p99": self._percentile(sorted_values, 99),
        }

    def get_timer_stats(self, name: str, labels: Optional[dict[str, str]] = None) -> dict[str, float]:
        key = self._make_key(name, labels)
        values = self._timers.get(key, [])
        if not values:
            return {}

        sorted_values = sorted(values)
        return {
            "count": len(values),
            "sum": sum(values),
            "min": min(values),
            "max": max(values),
            "mean": sum(values) / len(values),
            "p50": self._percentile(sorted_values, 50),
            "p95": self._percentile(sorted_values, 95),
            "p99": self._percentile(sorted_values, 99),
        }

    def _percentile(self, sorted_values: list[float], percentile: int) -> float:
        if not sorted_values:
            return 0.0
        index = int(len(sorted_values) * percentile / 100)
        return sorted_values[min(index, len(sorted_values) - 1)]

    def get_all_metrics(self) -> list[Metric]:
        return self._metrics.copy()

    def get_summary(self) -> dict[str, Any]:
        return {
            "counters": dict(self._counters),
            "gauges": dict(self._gauges),
            "histograms": {
                name: self.get_histogram_stats(name.split("{")[0], self._parse_labels(name))
                for name in self._histograms.keys()
            },
            "timers": {
                name: self.get_timer_stats(name.split("{")[0], self._parse_labels(name))
                for name in self._timers.keys()
            },
        }

    def _parse_labels(self, key: str) -> Optional[dict[str, str]]:
        if "{" not in key:
            return None
        label_str = key.split("{")[1].rstrip("}")
        if not label_str:
            return None
        labels = {}
        for pair in label_str.split(","):
            k, v = pair.split("=")
            labels[k] = v
        return labels

    def clear(self) -> None:
        self._metrics.clear()
        self._counters.clear()
        self._gauges.clear()
        self._histograms.clear()
        self._timers.clear()


class GlobalMetrics:
    _instance: Optional[MetricsCollector] = None

    @classmethod
    def get_collector(cls) -> MetricsCollector:
        if cls._instance is None:
            cls._instance = MetricsCollector()
        return cls._instance

    @classmethod
    def reset(cls) -> None:
        cls._instance = None
