"""
Metrics Module - Service metrics collection (Prometheus format)

Usage:
```python
from gmi_ieops.handler import get_metrics

metrics = get_metrics()
metrics.inc("requests_total")
metrics.set_gauge("active_requests", 5)
metrics.observe("latency_seconds", 0.123)

with metrics.timer("inference_latency_seconds"):
    pass

# Export as list of Prometheus lines (auto-merges external endpoint)
lines = metrics.export()
```
"""

import time
import threading
from typing import Dict, List, Optional, Tuple
from contextlib import contextmanager

from prometheus_client import Counter, Gauge, Histogram, Summary, CollectorRegistry, generate_latest

from ..utils import log


class Metrics:
    """Thread-safe metrics collection with Prometheus text format output."""
    
    DEFAULT_BUCKETS = (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0)
    
    def __init__(self, registry: Optional[CollectorRegistry] = None):
        self._lock = threading.RLock()
        self._registry = registry or CollectorRegistry()
        self._counters: Dict[str, Counter] = {}
        self._gauges: Dict[str, Gauge] = {}
        self._histograms: Dict[str, Histogram] = {}
        self._summaries: Dict[str, Summary] = {}
    
    def _get_or_create(self, store: dict, name: str, cls, labels: Optional[Dict[str, str]], **kwargs):
        """Get or create a metric with optional labels."""
        if name not in store:
            label_names = tuple(sorted(labels.keys())) if labels else ()
            store[name] = cls(name, f"{name}", label_names, registry=self._registry, **kwargs)
        return store[name].labels(**labels) if labels else store[name]
    
    # Counter
    def inc(self, name: str, value: float = 1.0, labels: Optional[Dict[str, str]] = None):
        """Increment a counter. Negative values are silently ignored (Counter only goes up)."""
        if value <= 0:
            return
        with self._lock:
            self._get_or_create(self._counters, name, Counter, labels).inc(value)
    
    def get_counter(self, name: str, labels: Optional[Dict[str, str]] = None) -> float:
        with self._lock:
            if name not in self._counters:
                return 0.0
            m = self._counters[name].labels(**labels) if labels else self._counters[name]
            return m._value.get()
    
    # Gauge
    def set_gauge(self, name: str, value: float, labels: Optional[Dict[str, str]] = None):
        """Set a gauge value."""
        with self._lock:
            self._get_or_create(self._gauges, name, Gauge, labels).set(value)
    
    def inc_gauge(self, name: str, value: float = 1.0, labels: Optional[Dict[str, str]] = None):
        """Increment a gauge."""
        with self._lock:
            self._get_or_create(self._gauges, name, Gauge, labels).inc(value)
    
    def dec_gauge(self, name: str, value: float = 1.0, labels: Optional[Dict[str, str]] = None):
        """Decrement a gauge."""
        self.inc_gauge(name, -value, labels)
    
    def get_gauge(self, name: str, labels: Optional[Dict[str, str]] = None) -> float:
        with self._lock:
            if name not in self._gauges:
                return 0.0
            m = self._gauges[name].labels(**labels) if labels else self._gauges[name]
            return m._value.get()
    
    # Histogram
    def register_histogram(self, name: str, buckets: Optional[Tuple[float, ...]] = None):
        """Pre-register a histogram with custom buckets."""
        with self._lock:
            if name not in self._histograms:
                self._histograms[name] = Histogram(
                    name, name, buckets=buckets or self.DEFAULT_BUCKETS, registry=self._registry
                )
    
    # Observe (histogram or summary)
    def observe(self, name: str, value: float, labels: Optional[Dict[str, str]] = None):
        """Record a value in histogram (if pre-registered) or summary."""
        with self._lock:
            if name in self._histograms:
                m = self._histograms[name].labels(**labels) if labels else self._histograms[name]
                m.observe(value)
            else:
                self._get_or_create(self._summaries, name, Summary, labels).observe(value)
    
    @contextmanager
    def timer(self, name: str, labels: Optional[Dict[str, str]] = None):
        """Context manager for timing operations."""
        start = time.perf_counter()
        try:
            yield
        finally:
            self.observe(name, time.perf_counter() - start, labels)
    
    # Convenience
    def track_request(self, success: bool = True, latency: float = None,
                      prompt_tokens: int = 0, completion_tokens: int = 0):
        """Track a complete request with common metrics.
        
        Negative token values are clamped to 0.
        """
        self.inc("requests_total")
        self.inc("requests_success_total" if success else "requests_failed_total")
        if latency is not None and latency >= 0:
            self.observe("request_latency_seconds", latency)
        if prompt_tokens > 0:
            self.inc("prompt_tokens_total", prompt_tokens)
        if completion_tokens > 0:
            self.inc("completion_tokens_total", completion_tokens)
    
    # Export
    def export(self, include_endpoint: bool = True) -> List[str]:
        """
        Export all metrics as list of Prometheus format lines.
        
        Args:
            include_endpoint: Auto-merge metrics from configured external endpoint.
        
        Returns:
            List of metric lines for Redis storage or joining with newlines.
        """
        with self._lock:
            text = generate_latest(self._registry).decode('utf-8')
        
        lines = [l for l in text.strip().split('\n') if l] if text else []
        
        if include_endpoint:
            endpoint_text = _fetch_from_endpoint()
            if endpoint_text:
                lines.extend(l for l in endpoint_text.strip().split('\n') if l)
        
        return lines
    
    def reset(self):
        """Reset all metrics."""
        with self._lock:
            self._registry = CollectorRegistry()
            self._counters.clear()
            self._gauges.clear()
            self._histograms.clear()
            self._summaries.clear()


# Global singleton
_metrics: Optional[Metrics] = None
_metrics_lock = threading.Lock()


def get_metrics() -> Metrics:
    """Get the global metrics instance."""
    global _metrics
    if _metrics is None:
        with _metrics_lock:
            if _metrics is None:
                _metrics = Metrics()
    return _metrics


def reset_global_metrics():
    """Reset the global metrics."""
    with _metrics_lock:
        if _metrics is not None:
            _metrics.reset()


def _fetch_from_endpoint() -> str:
    """Fetch raw Prometheus metrics from configured endpoint (internal)."""
    from ..config import env
    
    path, port = env.metrics.path, env.metrics.port
    if not port:
        return ""
    
    url = f"http://127.0.0.1:{port}/{path.lstrip('/')}"
    
    try:
        import urllib.request
        req = urllib.request.Request(url, headers={"Accept": "text/plain"})
        with urllib.request.urlopen(req, timeout=1.5) as resp:  # 1.5s timeout for local metrics fetch
            return resp.read().decode('utf-8')
    except Exception as e:
        log.get_logger().debug(f"Failed to fetch metrics from {url}: {e}")  # debug level to reduce log noise
        return ""
