"""HTTP routes for otelctx."""

from __future__ import annotations

from datetime import UTC, datetime
from typing import Optional

from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel

from common.version import __version__
from otelctx.db.repository import OtelRepository
from otelctx.ingest import normalize_log_payload, normalize_metric_payload, normalize_trace_payload


class IngestRequest(BaseModel):
    traces: list[dict[str, object]] | None = None
    spans: list[dict[str, object]] | None = None
    logs: list[dict[str, object]] | None = None
    metrics: list[dict[str, object]] | None = None


def ingest_payload(repository: OtelRepository, payload: dict[str, object]) -> dict[str, int]:
    summary = {"traces": 0, "spans": 0, "logs": 0, "metrics": 0}

    traces = normalize_trace_payload(payload)
    if traces:
        trace_summary = repository.ingest_traces([trace for trace in traces if trace.trace_id])
        summary["traces"] = trace_summary["traces"]
        summary["spans"] = trace_summary["spans"]

    logs = normalize_log_payload(payload)
    if logs:
        log_summary = repository.ingest_logs(logs)
        summary["logs"] = log_summary["logs"]

    metrics = normalize_metric_payload(payload)
    if metrics:
        metric_summary = repository.ingest_metrics(metrics)
        summary["metrics"] = metric_summary["metrics"]

    if not any(summary.values()):
        raise HTTPException(status_code=400, detail="payload does not contain traces, logs, or metrics")
    return summary


def _parse_dt(value: str | None) -> datetime | None:
    if not value:
        return None
    normalized = value.replace("Z", "+00:00")
    dt = datetime.fromisoformat(normalized)
    if dt.tzinfo is None:
        return dt.replace(tzinfo=UTC)
    return dt.astimezone(UTC)


def build_router(
    repository: OtelRepository,
    *,
    graph=None,
    tsdb=None,
    archive=None,
    dlq=None,
    sampler=None,
) -> APIRouter:
    """Create API routes bound to an otel repository instance."""
    from otelctx.graph import ServiceGraph

    _graph = graph if graph is not None else ServiceGraph()

    router = APIRouter(prefix="/api")

    # ------------------------------------------------------------------
    # Core
    # ------------------------------------------------------------------

    @router.get("/stats")
    async def stats() -> dict[str, int]:
        return repository.get_stats().to_dict()

    @router.get("/version")
    async def version() -> dict[str, str]:
        return {"version": __version__}

    # ------------------------------------------------------------------
    # Traces
    # ------------------------------------------------------------------

    @router.get("/traces")
    async def traces(
        service: Optional[str] = None,
        status: Optional[str] = None,
        start: Optional[str] = None,
        end: Optional[str] = None,
        limit: int = 20,
        offset: int = 0,
    ) -> list[dict[str, object]]:
        if service or status or start or end:
            return repository.get_traces_in_range(
                start=_parse_dt(start),
                end=_parse_dt(end),
                service=service,
                status=status,
                limit=limit,
            )
        return repository.list_traces(limit=limit, offset=offset)

    @router.get("/traces/{trace_id}")
    async def trace(trace_id: str) -> dict[str, object]:
        payload = repository.get_trace(trace_id)
        if payload is None:
            raise HTTPException(status_code=404, detail="trace not found")
        return payload

    # ------------------------------------------------------------------
    # Logs
    # ------------------------------------------------------------------

    @router.get("/logs")
    async def logs(
        service: Optional[str] = None,
        severity: Optional[str] = None,
        start: Optional[str] = None,
        end: Optional[str] = None,
        q: Optional[str] = None,
        limit: int = 20,
        offset: int = 0,
    ) -> list[dict[str, object]]:
        if q:
            return repository.search_logs(q, limit=limit)
        if service or severity or start or end:
            return repository.get_logs_in_range(
                start=_parse_dt(start),
                end=_parse_dt(end),
                service=service,
                severity=severity,
                limit=limit,
            )
        return repository.list_logs(limit=limit, offset=offset)

    @router.get("/logs/search")
    async def logs_search(q: str = Query(..., description="Substring to search in log bodies"), limit: int = 50) -> list[dict[str, object]]:
        return repository.search_logs(q, limit=limit)

    # ------------------------------------------------------------------
    # Metrics
    # ------------------------------------------------------------------

    @router.get("/metrics")
    async def metrics(
        service: Optional[str] = None,
        metric: Optional[str] = None,
        start: Optional[str] = None,
        end: Optional[str] = None,
        limit: int = 20,
        offset: int = 0,
    ) -> list[dict[str, object]]:
        if service or metric or start or end:
            return repository.get_metrics_in_range(
                start=_parse_dt(start),
                end=_parse_dt(end),
                service=service,
                metric_name=metric,
                limit=limit,
            )
        return repository.list_metrics(limit=limit, offset=offset)

    @router.get("/metrics/timeseries")
    async def metrics_timeseries(
        metric: str = Query(..., description="Metric name"),
        window: float = Query(300.0, description="Window in seconds"),
        step: float = Query(60.0, description="Bucket step in seconds"),
        service: Optional[str] = None,
    ) -> list[dict]:
        if tsdb is None:
            raise HTTPException(status_code=503, detail="TSDB not available")
        labels = {"service": service} if service else None
        return tsdb.aggregate_window(metric, window_seconds=window, step_seconds=step, labels=labels)

    # ------------------------------------------------------------------
    # Services
    # ------------------------------------------------------------------

    @router.get("/services")
    async def services() -> list[dict[str, object]]:
        return [service.to_dict() for service in repository.list_services()]

    # ------------------------------------------------------------------
    # Graph
    # ------------------------------------------------------------------

    @router.get("/graph")
    async def graph() -> dict:
        _graph.rebuild(repository)
        return _graph.get_graph()

    @router.get("/services/{service_name}/health")
    async def service_health(service_name: str) -> dict:
        _graph.rebuild(repository)
        result = _graph.get_service_health(service_name)
        if result is None:
            raise HTTPException(status_code=404, detail="service not found")
        return result

    # ------------------------------------------------------------------
    # Dashboard
    # ------------------------------------------------------------------

    @router.get("/dashboard")
    async def dashboard() -> dict[str, object]:
        return repository.get_dashboard_aggregation()

    # ------------------------------------------------------------------
    # Archive
    # ------------------------------------------------------------------

    @router.get("/archive/stats")
    async def archive_stats() -> dict:
        if archive is None:
            raise HTTPException(status_code=503, detail="archive not configured")
        return archive.storage_stats()

    @router.get("/archive/search")
    async def archive_search(
        q: Optional[str] = None,
        signal: str = "traces",
        days_back: int = 30,
        limit: int = 100,
    ) -> list[dict]:
        if archive is None:
            raise HTTPException(status_code=503, detail="archive not configured")
        return archive.search_cold(q, signal=signal, days_back=days_back, limit=limit)

    @router.get("/archive/list")
    async def archive_list(signal: Optional[str] = None) -> list[dict]:
        if archive is None:
            raise HTTPException(status_code=503, detail="archive not configured")
        return archive.list_archives(signal=signal)

    # ------------------------------------------------------------------
    # Dead Letter Queue
    # ------------------------------------------------------------------

    @router.get("/dlq")
    async def dlq_list(signal: Optional[str] = None, limit: int = 100) -> list[dict]:
        if dlq is None:
            raise HTTPException(status_code=503, detail="DLQ not configured")
        return dlq.list_entries(signal=signal, limit=limit)

    @router.post("/dlq/{entry_id}/ack")
    async def dlq_ack(entry_id: int) -> dict:
        if dlq is None:
            raise HTTPException(status_code=503, detail="DLQ not configured")
        success = dlq.ack(entry_id)
        if not success:
            raise HTTPException(status_code=404, detail="DLQ entry not found")
        return {"acked": entry_id}

    # ------------------------------------------------------------------
    # Sampler
    # ------------------------------------------------------------------

    @router.get("/sampler/stats")
    async def sampler_stats() -> dict:
        if sampler is None:
            raise HTTPException(status_code=503, detail="sampler not configured")
        return sampler.stats()

    # ------------------------------------------------------------------
    # Admin
    # ------------------------------------------------------------------

    @router.post("/admin/vacuum")
    async def vacuum() -> dict:
        repository.vacuum()
        return {"status": "ok"}

    # ------------------------------------------------------------------
    # Ingest
    # ------------------------------------------------------------------

    @router.post("/ingest")
    async def ingest(request: IngestRequest) -> dict[str, int]:
        return ingest_payload(repository, request.model_dump(exclude_none=True))

    @router.post("/otlp/v1/traces")
    async def ingest_otlp_api(payload: dict[str, object]) -> dict[str, int]:
        return ingest_payload(repository, payload)

    @router.post("/otlp/v1/logs")
    async def ingest_otlp_logs(payload: dict[str, object]) -> dict[str, int]:
        return ingest_payload(repository, payload)

    @router.post("/otlp/v1/metrics")
    async def ingest_otlp_metrics(payload: dict[str, object]) -> dict[str, int]:
        return ingest_payload(repository, payload)

    return router
