"""Tests for the public request-id surface promoted in PAPER-61.

The five public names (``REQUEST_ID_HEADER``, ``current_request_id``,
``generate_request_id``, ``set_request_id``, ``reset_request_id``)
graduated from private to public in mgf-common 0.40.0 so that
federation siblings (``mgf-django``, ``mgf-fastapi``, future
middleware-shape siblings) no longer need closed-box-leak carve-outs
to use them.

These tests pin the public-surface contract:

  - imports resolve from ``mgf.common`` top level (NOT from
    ``mgf.common._request_id``);
  - the ContextVar discipline (set returns Token, reset undoes the
    set) is honored;
  - the empty-string-outside-a-request default holds;
  - ``REQUEST_ID_HEADER`` is the canonical header constant.
"""

from __future__ import annotations

import asyncio
import uuid

from mgf.common import (
    REQUEST_ID_HEADER,
    current_request_id,
    generate_request_id,
    reset_request_id,
    set_request_id,
)


class TestPublicSurface:
    """PAPER-61: the names import from `mgf.common` top level."""

    def test_all_five_names_importable_from_mgf_common(self) -> None:
        import mgf.common

        for name in (
            "REQUEST_ID_HEADER",
            "current_request_id",
            "generate_request_id",
            "set_request_id",
            "reset_request_id",
        ):
            assert name in mgf.common.__all__, (
                f"{name!r} missing from mgf.common.__all__ — PAPER-61 promoted it to public surface"
            )
            assert hasattr(mgf.common, name)

    def test_request_id_header_is_x_request_id(self) -> None:
        assert REQUEST_ID_HEADER == "X-Request-Id"


class TestGenerateRequestId:
    def test_returns_uuid4_string(self) -> None:
        rid = generate_request_id()
        # UUID parses without error → it's a valid UUID-shape string.
        uuid.UUID(rid)

    def test_each_call_returns_unique(self) -> None:
        # Cheap collision check; 100 calls is more than enough to
        # catch a busted RNG.
        ids = {generate_request_id() for _ in range(100)}
        assert len(ids) == 100


class TestCurrentRequestId:
    def test_empty_string_outside_a_request(self) -> None:
        # No set_request_id active → empty string per the public
        # contract (NOT a raise).
        assert current_request_id() == ""


class TestSetResetCycle:
    def test_set_then_reset_restores_previous_value(self) -> None:
        # Outside-request baseline.
        assert current_request_id() == ""

        token = set_request_id("rid-1")
        assert current_request_id() == "rid-1"

        reset_request_id(token)
        assert current_request_id() == ""

    def test_nested_set_reset_unwinds_in_order(self) -> None:
        assert current_request_id() == ""

        token1 = set_request_id("rid-outer")
        assert current_request_id() == "rid-outer"

        token2 = set_request_id("rid-inner")
        assert current_request_id() == "rid-inner"

        reset_request_id(token2)
        assert current_request_id() == "rid-outer"

        reset_request_id(token1)
        assert current_request_id() == ""

    def test_async_propagation(self) -> None:
        # The ContextVar must be visible to async children of the
        # context that set it (the whole point of using a ContextVar
        # rather than a thread-local).
        async def child() -> str:
            return current_request_id()

        async def parent() -> str:
            token = set_request_id("rid-async")
            try:
                return await child()
            finally:
                reset_request_id(token)

        assert asyncio.run(parent()) == "rid-async"


class TestClosedBoxLeakClosed:
    """PAPER-61: the private surface should still exist (federation
    siblings on older mgf-common pins can still import it during
    their catch-up window), but the public surface MUST work
    independently — consumers MUST NOT need to touch the private
    `_request_id` module.
    """

    def test_public_imports_do_not_require_touching_private_module(self) -> None:
        # Synthesise the migration: a fresh consumer imports only
        # from mgf.common (no _request_id reach-in).
        from mgf.common import (
            REQUEST_ID_HEADER as H,
        )
        from mgf.common import (
            current_request_id as cur,
        )
        from mgf.common import (
            generate_request_id as gen,
        )
        from mgf.common import (
            reset_request_id as rst,
        )
        from mgf.common import (
            set_request_id as st,
        )

        rid = gen()
        token = st(rid)
        try:
            assert cur() == rid
            assert H == "X-Request-Id"
        finally:
            rst(token)
