"""PC-04: ``_redact_headers`` per-call cost.

The redactor iterates the request-headers dict, calls ``.lower()`` on
each key, then checks against the module-level ``_SECRET_HEADER_KEYS``
frozenset. For a 5-20 header dict (typical), this is sub-µs total —
the frozenset membership is O(1) and ``str.lower()`` is a tight C
call.

This pin catches regressions where the redaction shape grows
linear-in-set-size: e.g., a future refactor that replaces the
frozenset with a list (membership becomes O(N)), or that adds a
regex-based match per header. Either would slow ``_log_response``
linearly with the redaction-set size, which compounds across
high-throughput consumers.

Strategy: 20-header dict (realistic upper bound for a request with
auth + tenant + tracing + custom headers); 100 000 redactions;
assert per-call ≤ 2 µs.

Local baseline (Linux 6.17, Python 3.11): ~0.6 µs/call. Budget
``_PER_CALL_BUDGET_US = 2.0`` — ~3x cushion.

Marker: ``perf`` (registered in pyproject.toml). Excluded from the
default suite; run via ``pytest -m perf`` for the nightly sweep.
"""

from __future__ import annotations

import time

import pytest

from mgf.http._client import _redact_headers

# Per-call budget for `_redact_headers` over a 20-header dict.
# Local measurement is ~0.6 µs; the 2 µs ceiling covers CI noise
# and slower hardware. A breach signals the redaction shape grew
# non-O(1) — investigate `_SECRET_HEADER_KEYS` membership or a
# regex / list refactor.
_PER_CALL_BUDGET_US = 2.0


@pytest.mark.perf
class TestHeaderRedactionLookup:
    """Pin per-call cost of header redaction at request-log time."""

    def test_redaction_under_budget(self) -> None:
        # Realistic 20-header request: mix of redacted + non-redacted.
        headers = {
            "Authorization": "Bearer secret-xyz",
            "Cookie": "sess=abc; csrf=def",
            "X-Api-Key": "key-123",
            "X-Auth-Token": "tok-456",
            "X-Secret": "very-secret",
            "Proxy-Authorization": "Basic Zm9vOmJhcg==",
            "Accept": "application/json",
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "en-US",
            "Content-Type": "application/json",
            "User-Agent": "my-app/1.0 mgf-http/0.2.0",
            "X-Tenant": "acme",
            "X-Request-Id": "req-abc-123",
            "X-Trace-Id": "trace-xyz-456",
            "X-Forwarded-For": "10.0.0.1",
            "X-Real-Ip": "10.0.0.2",
            "Cache-Control": "no-cache",
            "If-Modified-Since": "Wed, 21 Oct 2026 07:28:00 GMT",
            "Range": "bytes=0-1023",
            "Origin": "https://app.example.com",
        }

        # Warm-up — pay any first-call dispatch costs.
        for _ in range(1000):
            _redact_headers(headers)

        n = 100_000
        start = time.perf_counter()
        for _ in range(n):
            _redact_headers(headers)
        elapsed = time.perf_counter() - start
        per_call_us = (elapsed / n) * 1_000_000

        assert per_call_us < _PER_CALL_BUDGET_US, (
            f"PC-04: _redact_headers budget breach: "
            f"{per_call_us:.3f} us/call (budget "
            f"{_PER_CALL_BUDGET_US:.1f} us). Investigate "
            f"src/mgf/http/_client.py::_redact_headers — likely the "
            f"frozenset became a list, OR a regex / multi-key match "
            f"was introduced. The shape must stay O(1) per header."
        )

    def test_redaction_correctness(self) -> None:
        """Sanity: secret-shaped headers are redacted; others pass
        through. Pins the contract the perf test rides on (a
        no-op redactor would trivially clear the budget)."""
        headers = {
            "Authorization": "Bearer secret",
            "X-Api-Key": "key",
            "Accept": "application/json",
        }
        out = _redact_headers(headers)
        assert out["Authorization"] == "<redacted>"
        assert out["X-Api-Key"] == "<redacted>"
        assert out["Accept"] == "application/json"
