"""Request-ID middleware + EventLog dispatch_id correlation.

Covers two halves of the same correlation story:

* ``RequestContextMiddleware`` generates / accepts an inbound request id,
  binds it onto the log-context ContextVar for the duration of the request,
  and echoes it back as the ``X-Request-Id`` response header.
* ``enrich_details_with_correlation`` merges the active ``dispatch_id`` and
  ``request_id`` into an ``EventLog.details`` dict so the persisted row can
  be cross-referenced with the stderr log lines emitted during the same
  dispatch.
"""

from __future__ import annotations

import re

import pytest
from fastapi.testclient import TestClient


_REQ_ID_PATTERN = re.compile(r"^req:[0-9a-f]{12}$")


def test_response_carries_generated_request_id(tmp_data_dir) -> None:  # noqa: ARG001
    """No inbound header → server generates ``req:<12 hex>`` and echoes it."""
    from notify_bridge_server.main import app

    with TestClient(app) as client:
        resp = client.get("/api/health")
        assert resp.status_code == 200
        req_id = resp.headers.get("X-Request-Id")
        assert req_id is not None
        assert _REQ_ID_PATTERN.match(req_id), (
            f"generated id {req_id!r} should match req:<12 hex>"
        )


def test_response_echoes_safe_inbound_request_id(tmp_data_dir) -> None:  # noqa: ARG001
    """A well-formed inbound ``X-Request-Id`` is preserved unchanged."""
    from notify_bridge_server.main import app

    inbound = "abc-123_XYZ_trace"
    with TestClient(app) as client:
        resp = client.get("/api/health", headers={"X-Request-Id": inbound})
        assert resp.status_code == 200
        assert resp.headers.get("X-Request-Id") == inbound


def test_colon_prefixed_inbound_id_is_replaced(tmp_data_dir) -> None:  # noqa: ARG001
    """``:`` is reserved for server-minted ids — a colon in the inbound value
    must trigger replacement so a client can't masquerade as ``disp:...``."""
    from notify_bridge_server.main import app

    with TestClient(app) as client:
        resp = client.get(
            "/api/health", headers={"X-Request-Id": "disp:fake12345678"},
        )
        assert resp.status_code == 200
        echoed = resp.headers.get("X-Request-Id", "")
        assert echoed != "disp:fake12345678"
        assert _REQ_ID_PATTERN.match(echoed)


@pytest.mark.parametrize(
    "bad_value",
    [
        # CRLF injection attempt — would split log lines / inject headers.
        "abc\r\ninjected: yes",
        # Way too long.
        "x" * 256,
        # Disallowed characters.
        "<script>alert(1)</script>",
        # Empty after stripping.
        "   ",
    ],
)
def test_unsafe_inbound_request_id_is_replaced(
    tmp_data_dir, bad_value: str,  # noqa: ARG001
) -> None:
    """An attacker-controlled id must not flow into logs verbatim."""
    from notify_bridge_server.main import app

    with TestClient(app) as client:
        resp = client.get("/api/health", headers={"X-Request-Id": bad_value})
        assert resp.status_code == 200
        echoed = resp.headers.get("X-Request-Id", "")
        assert echoed != bad_value, "unsafe id was passed through unchanged"
        assert _REQ_ID_PATTERN.match(echoed), (
            f"replacement id {echoed!r} should match req:<12 hex>"
        )


def test_enrich_details_merges_active_correlation_ids() -> None:
    """Within a ``bind_log_context`` block, the helper copies the active ids."""
    from notify_bridge_core.log_context import (
        bind_log_context,
        enrich_details_with_correlation,
    )

    with bind_log_context(
        dispatch_id="disp:deadbeef0001",
        request_id="req:cafecafe0002",
    ):
        result = enrich_details_with_correlation({"existing": "value"})

    assert result == {
        "existing": "value",
        "dispatch_id": "disp:deadbeef0001",
        "request_id": "req:cafecafe0002",
    }


def test_enrich_details_does_not_overwrite_explicit_keys() -> None:
    """If the caller pre-set a correlation key, the helper leaves it alone."""
    from notify_bridge_core.log_context import (
        bind_log_context,
        enrich_details_with_correlation,
    )

    with bind_log_context(dispatch_id="disp:newvalue00001"):
        result = enrich_details_with_correlation({"dispatch_id": "disp:pinned"})

    assert result["dispatch_id"] == "disp:pinned"


def test_enrich_details_no_context_returns_copy() -> None:
    """Outside any binding, the helper returns the dict unchanged but copied."""
    from notify_bridge_core.log_context import enrich_details_with_correlation

    original = {"key": "value"}
    result = enrich_details_with_correlation(original)
    assert result == original
    # Mutating the result must not leak into the caller's dict.
    result["extra"] = "added"
    assert "extra" not in original


def test_enrich_details_handles_none() -> None:
    """``None`` is accepted (callers may build details lazily)."""
    from notify_bridge_core.log_context import enrich_details_with_correlation

    assert enrich_details_with_correlation(None) == {}


def test_ensure_dispatch_id_generates_or_reuses() -> None:
    """Fresh call produces a new id; inside a bind it returns the bound one."""
    from notify_bridge_core.log_context import (
        bind_log_context,
        ensure_dispatch_id,
    )

    fresh = ensure_dispatch_id()
    assert fresh.startswith("disp:")
    assert len(fresh) == len("disp:") + 12

    with bind_log_context(dispatch_id="disp:bound00000001"):
        assert ensure_dispatch_id() == "disp:bound00000001"
