"""End-to-end redaction contract: secrets must be absent from the
ACTUAL serialized sink output — not merely from the in-memory payload.

Closes the T-002 gap from the 2026-05-29 architectural review and is the
regression contract for R-002 / F-002: the redactor previously walked
only dict/list/str, so secret-shaped strings inside tuples/sets and
secret-bearing fields on dataclasses/namedtuples reached the JSON sink
via the formatter's ``repr`` fallback, unredacted.

Each test routes a secret through ``JsonFormatter.format`` (the real
serialization path with the default redactor) and asserts the secret
SUBSTRING does not survive in the emitted JSON string.
"""

from __future__ import annotations

import logging
from collections import namedtuple
from dataclasses import dataclass
from typing import Any

import pytest

from mgf.common.observability.json_formatter import JsonFormatter
from mgf.common.observability.redaction import REDACTED_PLACEHOLDER, Redactor

# A value-PATTERN secret (caught by the Bearer regex regardless of key).
_TOKEN = "abcdefghij0123456789ABCDEFGHIJ"
_BEARER = f"Bearer {_TOKEN}"
# A KEY-only secret: not a shaped token, so it is redacted only when it
# sits under a known secret-bearing key (e.g. "password").
_PLAIN = "pl41n-but-under-a-secret-key-zzz"


def _make_record(extra: dict[str, Any]) -> logging.LogRecord:
    rec = logging.LogRecord(
        name="mgf.test",
        level=logging.INFO,
        pathname="t.py",
        lineno=1,
        msg="event",
        args=None,
        exc_info=None,
        func="f",
    )
    for k, v in extra.items():
        setattr(rec, k, v)
    return rec


def _emit(extra: dict[str, Any]) -> str:
    """Serialize a record with the given extras through the real path."""
    return JsonFormatter(redactor=Redactor.default()).format(_make_record(extra))


@dataclass
class _Creds:
    user: str
    password: str  # known secret-bearing key name


_NTCreds = namedtuple("_NTCreds", ["user", "password"])


class TestSerializedOutputHasNoSecrets:
    def test_dict_under_known_key(self) -> None:
        out = _emit({"creds": {"password": _PLAIN}})
        assert _PLAIN not in out
        assert REDACTED_PLACEHOLDER in out

    def test_list_with_shaped_value(self) -> None:
        out = _emit({"items": [_BEARER]})
        assert _TOKEN not in out

    def test_tuple_with_shaped_value(self) -> None:
        # F-002: tuples were not walked → the shaped token leaked.
        out = _emit({"parts": (_BEARER,)})
        assert _TOKEN not in out

    def test_set_with_shaped_value(self) -> None:
        # F-002: sets were not walked.
        out = _emit({"toks": {_BEARER}})
        assert _TOKEN not in out

    def test_bytes_with_shaped_value(self) -> None:
        out = _emit({"raw": _BEARER.encode()})
        assert _TOKEN not in out

    def test_dataclass_field_under_known_key(self) -> None:
        # F-002: dataclasses serialized via repr() → 'password' field leaked.
        out = _emit({"obj": _Creds(user="alice", password=_PLAIN)})
        assert _PLAIN not in out
        assert REDACTED_PLACEHOLDER in out
        assert "alice" in out  # non-secret field preserved

    def test_namedtuple_field_under_known_key(self) -> None:
        out = _emit({"obj": _NTCreds(user="bob", password=_PLAIN)})
        assert _PLAIN not in out
        assert REDACTED_PLACEHOLDER in out
        assert "bob" in out

    def test_nested_mixed_containers(self) -> None:
        # A realistic messy payload: dict→list→tuple→dataclass.
        payload = {
            "batch": [
                {"auth": (_BEARER,)},
                {"row": _Creds(user="carol", password=_PLAIN)},
            ],
        }
        out = _emit({"data": payload})
        assert _TOKEN not in out
        assert _PLAIN not in out
        assert "carol" in out

    @pytest.mark.parametrize("key", ["password", "token", "api_key", "secret"])
    def test_known_keys_redacted_inside_a_tuple_of_dicts(self, key: str) -> None:
        out = _emit({"rows": ({key: _PLAIN},)})
        assert _PLAIN not in out
