"""Metrics export -- Prometheus, JSON, and CSV export for external consumption.

Provides :class:`MetricsExporter` for programmatic access and a FastAPI
:data:`router` with ``/metrics/prometheus``, ``/metrics/json``, and
``/metrics/csv`` endpoints.
"""

from __future__ import annotations

import csv
import io
import logging
import time
from datetime import datetime, timezone
from typing import TYPE_CHECKING

from fastapi import APIRouter, Query
from fastapi.responses import PlainTextResponse, Response

if TYPE_CHECKING:
    from llmhosts.metrics.collector import MetricsCollector
    from llmhosts.router.cost import CostTracker

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Exporter
# ---------------------------------------------------------------------------


class MetricsExporter:
    """Export metrics in multiple formats for external consumption.

    Wraps a :class:`MetricsCollector` and :class:`CostTracker` to produce
    Prometheus text exposition, JSON summaries, and CSV request logs.
    """

    def __init__(self, collector: MetricsCollector, cost_tracker: CostTracker) -> None:
        self._collector = collector
        self._cost_tracker = cost_tracker

    # ------------------------------------------------------------------
    # Prometheus text exposition format
    # ------------------------------------------------------------------

    async def export_prometheus(self) -> str:
        """Export metrics in Prometheus text exposition format.

        Metrics emitted
        ---------------
        - ``llmhosts_requests_total``  counter  (labels: backend)
        - ``llmhosts_request_duration_seconds``  histogram summary (avg)
        - ``llmhosts_cache_hits_total``  counter
        - ``llmhosts_cache_misses_total``  counter
        - ``llmhosts_tokens_total``  counter  (labels: direction=input|output)
        - ``llmhosts_cost_dollars_total``  counter
        - ``llmhosts_savings_dollars_total``  counter
        - ``llmhosts_backend_healthy``  gauge  (labels: backend)
        - ``llmhosts_uptime_seconds``  gauge
        - ``llmhosts_requests_per_minute``  gauge
        - ``llmhosts_cache_hit_rate``  gauge
        """
        snap = self._collector.snapshot()
        savings_report = await self._cost_tracker.get_savings("all")

        lines: list[str] = []
        ts_ms = int(time.time() * 1000)

        def _counter(name: str, help_text: str, value: float | int, labels: str = "") -> None:
            lines.append(f"# HELP {name} {help_text}")
            lines.append(f"# TYPE {name} counter")
            label_part = f"{{{labels}}}" if labels else ""
            lines.append(f"{name}{label_part} {value} {ts_ms}")

        def _gauge(name: str, help_text: str, value: float | int, labels: str = "") -> None:
            lines.append(f"# HELP {name} {help_text}")
            lines.append(f"# TYPE {name} gauge")
            label_part = f"{{{labels}}}" if labels else ""
            lines.append(f"{name}{label_part} {value} {ts_ms}")

        # -- Counters --

        # Total requests per backend
        lines.append("# HELP llmhosts_requests_total Total requests handled.")
        lines.append("# TYPE llmhosts_requests_total counter")
        if snap.requests_by_backend:
            for backend, count in sorted(snap.requests_by_backend.items()):
                lines.append(f'llmhosts_requests_total{{backend="{backend}"}} {count} {ts_ms}')
        else:
            lines.append(f"llmhosts_requests_total {snap.total_requests} {ts_ms}")

        # Request duration (average as a gauge-style summary)
        _gauge(
            "llmhosts_request_duration_seconds",
            "Average request duration in seconds.",
            round(snap.avg_latency_ms / 1000.0, 6),
        )

        # Cache
        _counter("llmhosts_cache_hits_total", "Total cache hits.", self._collector._cache_hits)
        _counter("llmhosts_cache_misses_total", "Total cache misses.", self._collector._cache_misses)

        # Tokens (split by direction from savings report)
        lines.append("# HELP llmhosts_tokens_total Total tokens processed.")
        lines.append("# TYPE llmhosts_tokens_total counter")
        lines.append(f'llmhosts_tokens_total{{direction="total"}} {snap.total_tokens} {ts_ms}')

        # Cost & savings
        _counter("llmhosts_cost_dollars_total", "Total actual cost in USD.", round(savings_report.actual_cost, 6))
        _counter("llmhosts_savings_dollars_total", "Total savings vs cloud in USD.", round(savings_report.savings, 6))

        # -- Gauges --

        # Backend health (1 = healthy / present, 0 = absent)
        lines.append("# HELP llmhosts_backend_healthy Whether a backend is present and serving requests.")
        lines.append("# TYPE llmhosts_backend_healthy gauge")
        for backend in sorted(snap.requests_by_backend.keys()):
            lines.append(f'llmhosts_backend_healthy{{backend="{backend}"}} 1 {ts_ms}')

        _gauge("llmhosts_uptime_seconds", "Server uptime in seconds.", round(snap.uptime_seconds, 1))
        _gauge("llmhosts_requests_per_minute", "Current request rate per minute.", round(snap.requests_per_minute, 2))
        _gauge("llmhosts_cache_hit_rate", "Cache hit rate (0.0-1.0).", round(snap.cache_hit_rate, 4))
        _gauge(
            "llmhost_savings_percent",
            "Savings as a percentage of cloud-equivalent cost.",
            round(snap.savings_percent, 2),
        )

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

    # ------------------------------------------------------------------
    # JSON export
    # ------------------------------------------------------------------

    async def export_json(self) -> dict:
        """Export all metrics as a JSON-serialisable dict.

        Returns a dict with keys: ``snapshot``, ``savings``, ``recent_requests``.
        """
        snap = self._collector.snapshot()
        savings_report = await self._cost_tracker.get_savings("all")
        recent = await self._cost_tracker.get_request_log(limit=50)

        return {
            "exported_at": datetime.now(tz=timezone.utc).isoformat(),
            "snapshot": snap.model_dump(),
            "savings": savings_report.model_dump(),
            "recent_requests": [r.model_dump(mode="json") for r in recent],
        }

    # ------------------------------------------------------------------
    # CSV export
    # ------------------------------------------------------------------

    async def export_csv(self, period: str = "day") -> str:
        """Export request log as CSV for the given period.

        Parameters
        ----------
        period:
            One of ``"day"``, ``"week"``, ``"month"``, ``"all"``.
            Controls how many rows are returned (max 10 000).
        """
        period_limits = {"day": 1_000, "week": 5_000, "month": 10_000, "all": 10_000}
        limit = period_limits.get(period, 1_000)

        requests = await self._cost_tracker.get_request_log(limit=limit)

        # Filter by period if not "all"
        if period != "all":
            from datetime import timedelta

            now = datetime.now(tz=timezone.utc)
            deltas = {"day": timedelta(days=1), "week": timedelta(weeks=1), "month": timedelta(days=30)}
            delta = deltas.get(period, timedelta(days=1))
            cutoff = now - delta
            requests = [r for r in requests if r.timestamp.replace(tzinfo=timezone.utc) >= cutoff]

        buf = io.StringIO()
        writer = csv.writer(buf)

        # Header
        writer.writerow(
            [
                "request_id",
                "timestamp",
                "model_requested",
                "model_used",
                "backend_type",
                "routing_tier",
                "routing_reasoning",
                "input_tokens",
                "output_tokens",
                "actual_cost",
                "cloud_equivalent_cost",
                "latency_ms",
                "cached",
            ]
        )

        # Data rows
        for req in requests:
            writer.writerow(
                [
                    req.request_id,
                    req.timestamp.isoformat() if isinstance(req.timestamp, datetime) else str(req.timestamp),
                    req.model_requested,
                    req.model_used,
                    req.backend_type,
                    req.routing_tier,
                    req.routing_reasoning,
                    req.input_tokens,
                    req.output_tokens,
                    f"{req.actual_cost:.6f}",
                    f"{req.cloud_equivalent_cost:.6f}",
                    f"{req.latency_ms:.2f}",
                    "true" if req.cached else "false",
                ]
            )

        return buf.getvalue()


# ---------------------------------------------------------------------------
# Singleton holder for route dependency injection
# ---------------------------------------------------------------------------

_exporter: MetricsExporter | None = None


def set_exporter(exporter: MetricsExporter) -> None:
    """Register the global :class:`MetricsExporter` instance for route handlers."""
    global _exporter
    _exporter = exporter


def get_exporter() -> MetricsExporter:
    """Retrieve the global :class:`MetricsExporter`. Raises if not set."""
    if _exporter is None:
        raise RuntimeError("MetricsExporter not initialised -- call set_exporter() at startup")
    return _exporter


# ---------------------------------------------------------------------------
# FastAPI routes
# ---------------------------------------------------------------------------

router = APIRouter(tags=["metrics"])


@router.get("/metrics/prometheus", response_class=PlainTextResponse)
async def prometheus_metrics() -> Response:
    """Prometheus-compatible metrics endpoint.

    Scrape this with ``scrape_configs`` in ``prometheus.yml``::

        - job_name: llmhost
          static_configs:
            - targets: ["localhost:4000"]
          metrics_path: /metrics/prometheus
    """
    exporter = get_exporter()
    text = await exporter.export_prometheus()
    return PlainTextResponse(
        content=text,
        media_type="text/plain; version=0.0.4; charset=utf-8",
    )


@router.get("/metrics/json")
async def json_metrics() -> dict:
    """JSON metrics export -- snapshot, savings, and recent requests."""
    exporter = get_exporter()
    return await exporter.export_json()


@router.get("/metrics/csv")
async def csv_metrics(
    period: str = Query(default="day", description="Time period: day, week, month, all"),
) -> Response:
    """CSV export of request log for the given period.

    Returns a downloadable CSV file with full routing decision and cost data.
    """
    valid_periods = {"day", "week", "month", "all"}
    if period not in valid_periods:
        period = "day"

    exporter = get_exporter()
    csv_text = await exporter.export_csv(period=period)

    filename = f"llmhost_requests_{period}_{datetime.now(tz=timezone.utc).strftime('%Y%m%d')}.csv"
    return Response(
        content=csv_text,
        media_type="text/csv",
        headers={"Content-Disposition": f'attachment; filename="{filename}"'},
    )
