"""Tests for ``mgf.common.fastapi.RequestIdMiddleware``."""

from __future__ import annotations

import re

from fastapi import FastAPI
from fastapi.testclient import TestClient

from mgf.fastapi import (
    REQUEST_ID_HEADER,
    RequestIdMiddleware,
    current_request_id,
)


def _build_app() -> FastAPI:
    app = FastAPI()
    app.add_middleware(RequestIdMiddleware)

    @app.get("/echo-id")
    def _echo() -> dict[str, str]:
        return {"id_via_contextvar": current_request_id()}

    return app


class TestRequestIdMiddleware:
    def test_generates_uuid_when_header_absent(self) -> None:
        with TestClient(_build_app()) as client:
            response = client.get("/echo-id")
        assert response.status_code == 200
        request_id = response.headers[REQUEST_ID_HEADER]
        # UUID4 shape.
        assert re.match(
            r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            request_id,
        )
        # Echoed via contextvar inside the handler.
        assert response.json()["id_via_contextvar"] == request_id

    def test_forwards_inbound_header(self) -> None:
        with TestClient(_build_app()) as client:
            response = client.get(
                "/echo-id",
                headers={REQUEST_ID_HEADER: "client-supplied-123"},
            )
        assert response.headers[REQUEST_ID_HEADER] == "client-supplied-123"
        assert response.json()["id_via_contextvar"] == "client-supplied-123"

    def test_lowercase_inbound_header_accepted(self) -> None:
        with TestClient(_build_app()) as client:
            response = client.get(
                "/echo-id",
                headers={"x-request-id": "lower-case-456"},
            )
        # We echo using the canonical name.
        assert response.headers[REQUEST_ID_HEADER] == "lower-case-456"

    def test_each_request_gets_fresh_id(self) -> None:
        with TestClient(_build_app()) as client:
            r1 = client.get("/echo-id")
            r2 = client.get("/echo-id")
        assert r1.headers[REQUEST_ID_HEADER] != r2.headers[REQUEST_ID_HEADER]


class TestCurrentRequestIdOutsideRequest:
    def test_returns_empty_string_outside_request(self) -> None:
        # No request active; default is "".
        assert current_request_id() == ""


class TestRequestIdInjectionGuard:
    """SH-01: malicious inbound X-Request-Id is rejected before
    reflection. Twin of mgf-django SH-01 — both siblings share the
    contextvar and the same validation rule. Defence-in-depth
    against response-splitting (CR/LF in the reflected header
    could inject a `Set-Cookie:` line).
    """

    _UUID4 = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")

    def test_crlf_injection_rejected(self) -> None:
        with TestClient(_build_app()) as client:
            response = client.get(
                "/echo-id",
                headers={REQUEST_ID_HEADER: "foo\r\nSet-Cookie: m=1"},
            )
        # Fresh UUID — NOT the attacker-supplied value.
        rid = response.headers[REQUEST_ID_HEADER]
        assert self._UUID4.match(rid), (
            f"Expected UUID4 (rejection generated fresh id), got: {rid!r}"
        )
        # No Set-Cookie from the injection.
        assert "m=1" not in response.headers.get("set-cookie", "")

    def test_lf_only_rejected(self) -> None:
        with TestClient(_build_app()) as client:
            response = client.get(
                "/echo-id",
                headers={REQUEST_ID_HEADER: "foo\nbar"},
            )
        rid = response.headers[REQUEST_ID_HEADER]
        assert self._UUID4.match(rid)

    def test_nul_rejected(self) -> None:
        with TestClient(_build_app()) as client:
            response = client.get(
                "/echo-id",
                headers={REQUEST_ID_HEADER: "foo\x00bar"},
            )
        rid = response.headers[REQUEST_ID_HEADER]
        assert self._UUID4.match(rid)

    def test_overlong_value_rejected(self) -> None:
        with TestClient(_build_app()) as client:
            response = client.get(
                "/echo-id",
                headers={REQUEST_ID_HEADER: "a" * 200},
            )
        rid = response.headers[REQUEST_ID_HEADER]
        assert self._UUID4.match(rid)

    def test_legitimate_values_pass(self) -> None:
        for value in (
            "test-correlation-abc",
            "01H8XGJ3M5B9YDQ7E2N4F6V8P0",
            "uuid:550e8400-e29b-41d4-a716-446655440000",
        ):
            with TestClient(_build_app()) as client:
                response = client.get(
                    "/echo-id",
                    headers={REQUEST_ID_HEADER: value},
                )
            assert response.headers[REQUEST_ID_HEADER] == value, (
                f"Clean value {value!r} got rewritten — guard too aggressive"
            )
