"""Property test: the Redactor is idempotent + key-safe.

Rule **TS-13** — code that round-trips data MUST be covered by
property tests. The Redactor walks a payload in place; two key
invariants:

  1. **Idempotence.** Running the redactor twice produces the same
     result as once. (A second pass MUST NOT re-introduce a key
     that the first pass scrubbed.)

  2. **Key safety.** The set of dict keys is preserved by
     redaction — only values change. Without this, a refactor that
     accidentally pops a key would corrupt downstream consumers
     who index by key.

  3. **Sensitive keys ARE scrubbed.** Any key whose name matches a
     known secret-bearing pattern has its value replaced by the
     redaction sentinel.

The strategy generates dicts with mixed scalar / nested types,
including some keys we know should be redacted and some we know
should not.
"""

from __future__ import annotations

from copy import deepcopy
from typing import Any

import pytest
from hypothesis import HealthCheck, given, settings

from mgf.common.observability.redaction import Redactor
from tests.unit.observability._strategies import (
    _SENSITIVE_KEYS,
    _payload,
)


@given(payload=_payload())
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], max_examples=80)
def test_redactor_is_idempotent(payload: dict[str, Any]) -> None:
    """Running the redactor twice produces the same result as once."""
    r = Redactor.default()
    once = deepcopy(payload)
    r.redact(once)
    twice = deepcopy(once)
    r.redact(twice)
    assert once == twice, "Redactor is not idempotent"


@given(payload=_payload())
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], max_examples=80)
def test_redactor_preserves_keys(payload: dict[str, Any]) -> None:
    """Redaction never adds or removes keys — only changes values."""
    r = Redactor.default()
    original_keys = set(payload.keys())
    r.redact(payload)
    assert set(payload.keys()) == original_keys, "Redactor changed the dict's key set"


@pytest.mark.parametrize("sensitive_key", _SENSITIVE_KEYS)
def test_redactor_scrubs_known_sensitive_keys(sensitive_key: str) -> None:
    """Every well-known sensitive-key name has its value replaced."""
    r = Redactor.default()
    payload = {sensitive_key: "sensitive_value", "other": "kept"}
    r.redact(payload)
    assert payload[sensitive_key] != "sensitive_value", (
        f"Redactor failed to scrub {sensitive_key!r}"
    )
    assert payload["other"] == "kept", "Redactor incorrectly modified a non-sensitive value"


def test_redactor_scrubs_aws_access_key_value_pattern() -> None:
    """TS-13 / SC-09: an AWS access-key-id VALUE (``AKIA…``) is redacted by the
    builtin value-pattern group even under a non-sensitive key name."""
    r = Redactor.default()
    payload = {"note": "AKIAIOSFODNN7EXAMPLE"}
    r.redact(payload)
    assert payload["note"] != "AKIAIOSFODNN7EXAMPLE", (
        "AWS access-key value pattern (AKIA…) was not redacted"
    )
