"""Lightweight Prometheus metrics server.

Exposes /metrics endpoint in Prometheus text exposition format.
Uses stdlib http.server - no external dependencies.
"""

from __future__ import annotations

import threading
from http.server import HTTPServer, BaseHTTPRequestHandler


class _Counter:
    __slots__ = ("_value", "_lock")

    def __init__(self) -> None:
        self._value = 0.0
        self._lock = threading.Lock()

    def inc(self, amount: float = 1.0) -> None:
        with self._lock:
            self._value += amount

    @property
    def value(self) -> float:
        return self._value


class _Gauge:
    __slots__ = ("_value", "_lock")

    def __init__(self) -> None:
        self._value = 0.0
        self._lock = threading.Lock()

    def set(self, value: float) -> None:
        with self._lock:
            self._value = value

    def inc(self, amount: float = 1.0) -> None:
        with self._lock:
            self._value += amount

    @property
    def value(self) -> float:
        return self._value


class _Histogram:
    """Simple histogram with fixed buckets."""
    __slots__ = ("_buckets", "_sum", "_count", "_lock", "_bucket_bounds")

    def __init__(self, buckets: tuple[float, ...] = (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0)) -> None:
        self._bucket_bounds = buckets
        self._buckets = [0] * len(buckets)
        self._sum = 0.0
        self._count = 0
        self._lock = threading.Lock()

    def observe(self, value: float) -> None:
        with self._lock:
            self._sum += value
            self._count += 1
            for i, bound in enumerate(self._bucket_bounds):
                if value <= bound:
                    self._buckets[i] += 1
                    break  # Only increment the first matching bucket; render() cumulates

    def render(self, name: str, labels: str = "") -> str:
        lines = []
        with self._lock:
            cumulative = 0
            for i, bound in enumerate(self._bucket_bounds):
                cumulative += self._buckets[i]
                lines.append(f'{name}_bucket{{le="{bound}"{labels}}} {cumulative}')
            lines.append(f'{name}_bucket{{le="+Inf"{labels}}} {self._count}')
            label_str = labels.lstrip(",")
            if label_str:
                lines.append(f"{name}_sum{{{label_str}}} {self._sum}")
                lines.append(f"{name}_count{{{label_str}}} {self._count}")
            else:
                lines.append(f"{name}_sum {self._sum}")
                lines.append(f"{name}_count {self._count}")
        return "\n".join(lines)


class MetricsCollector:
    """Thread-safe metrics collection."""

    def __init__(self) -> None:
        self._counters: dict[str, _Counter] = {}
        self._gauges: dict[str, _Gauge] = {}
        self._histograms: dict[str, _Histogram] = {}
        self._lock = threading.Lock()

    def counter(self, name: str) -> _Counter:
        # Fast path: metric already exists (no lock needed for read)
        c = self._counters.get(name)
        if c is not None:
            return c
        with self._lock:
            if name not in self._counters:
                self._counters[name] = _Counter()
            return self._counters[name]

    def gauge(self, name: str) -> _Gauge:
        g = self._gauges.get(name)
        if g is not None:
            return g
        with self._lock:
            if name not in self._gauges:
                self._gauges[name] = _Gauge()
            return self._gauges[name]

    def histogram(self, name: str) -> _Histogram:
        h = self._histograms.get(name)
        if h is not None:
            return h
        with self._lock:
            if name not in self._histograms:
                self._histograms[name] = _Histogram()
            return self._histograms[name]

    def render(self) -> str:
        """Render all metrics in Prometheus text format."""
        lines = []
        with self._lock:
            for name, c in sorted(self._counters.items()):
                lines.append(f"# TYPE {name} counter")
                lines.append(f"{name} {c.value}")
            for name, g in sorted(self._gauges.items()):
                lines.append(f"# TYPE {name} gauge")
                lines.append(f"{name} {g.value}")
            for name, h in sorted(self._histograms.items()):
                lines.append(f"# TYPE {name} histogram")
                lines.append(h.render(name))
        return "\n".join(lines) + "\n"


class MetricsServer:
    """HTTP server exposing /metrics in Prometheus text format.

    Usage:
        collector = MetricsCollector()
        server = MetricsServer(collector, port=9090)
        server.start()
        # ... update metrics ...
        collector.counter("orders_submitted").inc()
        collector.gauge("open_orders").set(5)
        # ... later ...
        server.stop()
    """

    def __init__(self, collector: MetricsCollector, port: int = 9090) -> None:
        self.collector = collector
        self.port = port
        self._server: HTTPServer | None = None
        self._thread: threading.Thread | None = None

    def start(self) -> None:
        collector = self.collector

        class Handler(BaseHTTPRequestHandler):
            def do_GET(self):
                if self.path == "/metrics":
                    body = collector.render().encode()
                    self.send_response(200)
                    self.send_header("Content-Type", "text/plain; version=0.0.4")
                    self.send_header("Content-Length", str(len(body)))
                    self.end_headers()
                    self.wfile.write(body)
                else:
                    self.send_response(404)
                    self.end_headers()

            def log_message(self, format, *args):
                pass  # Suppress request logs

        self._server = HTTPServer(("127.0.0.1", self.port), Handler)
        self._thread = threading.Thread(target=self._server.serve_forever, daemon=True)
        self._thread.start()

    def stop(self) -> None:
        if self._server:
            self._server.shutdown()
            self._server = None


def update_engine_metrics(collector: MetricsCollector, engine) -> None:
    """Update metrics from engine status. Call each tick cycle."""
    status = engine.status()
    collector.gauge("horizon_open_orders").set(status.open_orders)
    collector.gauge("horizon_active_positions").set(status.active_positions)
    collector.gauge("horizon_total_realized_pnl").set(status.total_realized_pnl)
    collector.gauge("horizon_total_unrealized_pnl").set(status.total_unrealized_pnl)
    collector.gauge("horizon_daily_pnl").set(status.daily_pnl)
    collector.gauge("horizon_kill_switch").set(1.0 if status.kill_switch_active else 0.0)
    collector.gauge("horizon_uptime_secs").set(status.uptime_secs)
