"""
FastAPI Prometheus instrumentation factory.

Provides a configured Instrumentator for automatic HTTP metrics collection.
"""

from prometheus_fastapi_instrumentator import Instrumentator


def create_instrumentator(
    service_name: str,
    latency_buckets: tuple[float, ...] = (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10),
    excluded_handlers: list[str] | None = None,
) -> Instrumentator:
    """
    Create a configured Prometheus Instrumentator for a FastAPI application.

    Args:
        service_name: Name of the service (used for metric labels)
        latency_buckets: Histogram buckets for request latency (in seconds)
        excluded_handlers: List of endpoint paths to exclude from instrumentation.
                          Defaults to ["/health", "/metrics", "/docs", "/openapi.json", "/redoc"]

    Returns:
        Configured Instrumentator instance

    Usage:
        from common.metrics import create_instrumentator

        @asynccontextmanager
        async def lifespan(app: FastAPI):
            # ... other setup ...
            instrumentator = create_instrumentator(service_name="my-service")
            instrumentator.instrument(app).expose(app, include_in_schema=False)
            yield
    """
    if excluded_handlers is None:
        excluded_handlers = ["/health", "/metrics", "/docs", "/openapi.json", "/redoc"]

    instrumentator = Instrumentator(
        should_group_status_codes=True,
        should_ignore_untemplated=True,
        should_respect_env_var=False,
        should_instrument_requests_inprogress=True,
        excluded_handlers=excluded_handlers,
        inprogress_name="http_requests_inprogress",
        inprogress_labels=True,
    )

    # Add default metrics
    instrumentator.add(
        _request_latency_histogram(
            metric_name="http_request_duration_seconds",
            buckets=latency_buckets,
        )
    )
    instrumentator.add(
        _request_count(
            metric_name="http_requests_total",
        )
    )

    return instrumentator


def _get_exemplar() -> dict:
    """
    Get trace_id as a Prometheus exemplar if OpenTelemetry is active.

    Returns {"trace_id": hex_trace_id} or {} if OTel is not available.
    """
    try:
        from opentelemetry import trace

        span = trace.get_current_span()
        ctx = span.get_span_context()
        if ctx and ctx.trace_id != 0:
            return {"trace_id": format(ctx.trace_id, "032x")}
    except Exception:
        pass
    return {}


def _request_latency_histogram(
    metric_name: str = "http_request_duration_seconds",
    buckets: tuple[float, ...] = (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10),
):
    """
    Create a histogram metric for request latency.
    """
    from prometheus_client import Histogram
    from prometheus_fastapi_instrumentator.metrics import Info

    histogram = Histogram(
        metric_name,
        "HTTP request duration in seconds",
        labelnames=["method", "handler", "status"],
        buckets=buckets,
    )

    def instrumentation(info: Info):
        if info.modified_handler:
            handler = info.modified_handler
        else:
            handler = info.request.url.path

        exemplar = _get_exemplar()
        histogram.labels(
            method=info.request.method,
            handler=handler,
            status=info.response.status_code if info.response else "unknown",
        ).observe(info.modified_duration, exemplar)

    return instrumentation


def _request_count(metric_name: str = "http_requests_total"):
    """
    Create a counter metric for total requests.
    """
    from prometheus_client import Counter
    from prometheus_fastapi_instrumentator.metrics import Info

    counter = Counter(
        metric_name,
        "Total HTTP requests",
        labelnames=["method", "handler", "status"],
    )

    def instrumentation(info: Info):
        if info.modified_handler:
            handler = info.modified_handler
        else:
            handler = info.request.url.path

        counter.labels(
            method=info.request.method,
            handler=handler,
            status=info.response.status_code if info.response else "unknown",
        ).inc()

    return instrumentation
