"""
Tests for RunwayMiddleware behaviour and metric correctness.
"""

import asyncio
from typing import List

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from asgi_runway import REQUESTS_IN_FLIGHT, REQUESTS_TOTAL, RunwayMiddleware, metrics_router


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _in_flight() -> float:
    """Read the current in-flight gauge value."""
    return REQUESTS_IN_FLIGHT._value.get()


def _requests_total(method: str, status: str) -> float:
    """Read total requests for a given method+status combination."""
    from asgi_runway._metrics import APP_NAME
    return REQUESTS_TOTAL.labels(app_name=APP_NAME, method=method, status=status)._value.get()


# ---------------------------------------------------------------------------
# Basic request tracking
# ---------------------------------------------------------------------------

def test_in_flight_returns_to_zero_after_request(client):
    before = _in_flight()
    client.get("/hello")
    assert _in_flight() == before  # must return to baseline


def test_total_counter_incremented(client):
    before = _requests_total("GET", "200")
    client.get("/hello")
    assert _requests_total("GET", "200") == before + 1


def test_error_decrements_in_flight(client):
    before = _in_flight()
    client.get("/error")  # raises RuntimeError → 500
    assert _in_flight() == before


def test_error_records_500_status(client):
    before = _requests_total("GET", "500")
    client.get("/error")
    assert _requests_total("GET", "500") == before + 1


# ---------------------------------------------------------------------------
# Exclude paths
# ---------------------------------------------------------------------------

def test_metrics_endpoint_not_counted(client):
    before = _requests_total("GET", "200")
    client.get("/metrics")
    # /metrics is excluded by default — total counter must not change
    assert _requests_total("GET", "200") == before


def test_custom_exclude_path():
    app = FastAPI()
    app.add_middleware(RunwayMiddleware, exclude_paths=["/ping"])
    app.include_router(metrics_router)

    @app.get("/ping")
    def ping():
        return "pong"

    with TestClient(app) as c:
        before = _requests_total("GET", "200")
        c.get("/ping")
        assert _requests_total("GET", "200") == before


# ---------------------------------------------------------------------------
# Route groups
# ---------------------------------------------------------------------------

def test_route_groups_track_per_label():
    from asgi_runway import REQUESTS_IN_FLIGHT_BY_ROUTE

    app = FastAPI()
    app.add_middleware(
        RunwayMiddleware,
        route_groups=[(r"^/api/infer", "inference"), (r"^/api/embed", "embedding")],
    )

    @app.get("/api/infer")
    def infer():
        return {}

    @app.get("/api/embed")
    def embed():
        return {}

    @app.get("/api/other")
    def other():
        return {}

    from asgi_runway._metrics import APP_NAME

    with TestClient(app) as c:
        infer_before = REQUESTS_IN_FLIGHT_BY_ROUTE.labels(app_name=APP_NAME, route="inference")._value.get()
        embed_before = REQUESTS_IN_FLIGHT_BY_ROUTE.labels(app_name=APP_NAME, route="embedding")._value.get()

        c.get("/api/infer")
        c.get("/api/embed")
        c.get("/api/other")  # no matching group → no per-route label

        assert REQUESTS_IN_FLIGHT_BY_ROUTE.labels(app_name=APP_NAME, route="inference")._value.get() == infer_before
        assert REQUESTS_IN_FLIGHT_BY_ROUTE.labels(app_name=APP_NAME, route="embedding")._value.get() == embed_before


# ---------------------------------------------------------------------------
# Non-HTTP scopes (WebSocket, lifespan) are passed through untouched
# ---------------------------------------------------------------------------

def test_non_http_scope_passthrough():
    received_scopes: List[dict] = []

    async def inner_app(scope, receive, send):
        received_scopes.append(scope)

    middleware = RunwayMiddleware(inner_app)

    async def run():
        fake_scope = {"type": "lifespan"}
        await middleware(fake_scope, None, None)

    asyncio.run(run())
    assert received_scopes[0]["type"] == "lifespan"
    assert _in_flight() == 0.0  # lifespan must not affect counter


# ---------------------------------------------------------------------------
# Metrics endpoint returns valid Prometheus text
# ---------------------------------------------------------------------------

def test_metrics_endpoint_returns_prometheus_format(client):
    client.get("/hello")  # ensure there is at least one data point
    resp = client.get("/metrics")
    assert resp.status_code == 200
    assert "runway_requests_in_flight" in resp.text
    assert "runway_requests_total" in resp.text
    assert "runway_request_duration_seconds" in resp.text


def test_metrics_content_type(client):
    resp = client.get("/metrics")
    assert "text/plain" in resp.headers["content-type"]
