"""Tests for Issue #177: Privacy hardening batch.

Tests the security improvements from the hardening batch that belong to the
privacy module:
- Medium severity: audit PII sanitization, watermark secret_key warnings
- Low severity: capability TTL reduction, canary strip_canaries warning,
  extraction rate limiting

Note: Tests for federation-specific hardening (sensitive domain classification,
budget consumption, consent store LRU, aggregated sync logging) live in
our-federation.
"""

import secrets

import pytest

# =============================================================================
# MEDIUM SEVERITY TESTS
# =============================================================================


class TestAuditPIISanitization:
    """Test PII sanitization in audit metadata."""

    def test_email_sanitized(self):
        """Email addresses are redacted."""
        from our_privacy.audit import sanitize_metadata

        result = sanitize_metadata({"contact": "user@example.com"})
        assert "[PII_REDACTED]" in result["contact"]
        assert "user@example.com" not in result["contact"]

    def test_phone_sanitized(self):
        """Phone numbers are redacted."""
        from our_privacy.audit import sanitize_metadata

        result = sanitize_metadata({"phone": "555-123-4567"})
        assert "[PII_REDACTED]" in result["phone"]

    def test_ssn_sanitized(self):
        """SSN key is sensitive and value is fully redacted."""
        from our_privacy.audit import sanitize_metadata

        # "ssn" is a sensitive key, so value is fully redacted (not PII pattern)
        result = sanitize_metadata({"ssn": "123-45-6789"})
        assert result["ssn"] == "[REDACTED]"

    def test_sensitive_keys_fully_redacted(self):
        """Keys named after sensitive data are fully redacted."""
        from our_privacy.audit import sanitize_metadata

        result = sanitize_metadata(
            {
                "password": "secret123",
                "api_key": "sk-abc123",
                "token": "bearer_xyz",
            }
        )
        assert result["password"] == "[REDACTED]"
        assert result["api_key"] == "[REDACTED]"
        assert result["token"] == "[REDACTED]"

    def test_non_sensitive_preserved(self):
        """Non-sensitive data is preserved."""
        from our_privacy.audit import sanitize_metadata

        result = sanitize_metadata(
            {
                "action": "login",
                "timestamp": "2024-01-01T00:00:00Z",
                "resource_id": "belief_123",
            }
        )
        assert result["action"] == "login"
        assert result["timestamp"] == "2024-01-01T00:00:00Z"
        assert result["resource_id"] == "belief_123"

    def test_audit_event_auto_sanitizes(self):
        """AuditEvent automatically sanitizes metadata on creation."""
        from our_privacy.audit import AuditEvent, AuditEventType

        event = AuditEvent(
            event_type=AuditEventType.ACCESS_DENIED,
            actor_did="did:example:alice",
            resource="belief_123",
            action="read",
            success=False,
            metadata={"email": "alice@example.com", "reason": "unauthorized"},
        )

        # Email should be sanitized
        assert "[PII_REDACTED]" in event.metadata["email"]
        # Non-PII preserved
        assert event.metadata["reason"] == "unauthorized"


class TestWatermarkSecurityWarnings:
    """Test WatermarkRegistry security documentation."""

    def test_minimum_key_length_enforced(self):
        """Short secret keys are rejected."""
        from our_privacy.watermark import WatermarkRegistry

        with pytest.raises(ValueError, match="at least 16 bytes"):
            WatermarkRegistry(secret_key=b"short")

    def test_valid_key_accepted(self):
        """Valid length keys work."""
        from our_privacy.watermark import WatermarkRegistry

        registry = WatermarkRegistry(secret_key=secrets.token_bytes(32))
        assert registry.secret_key is not None

    def test_repr_hides_secret(self):
        """__repr__ doesn't expose the secret key."""
        from our_privacy.watermark import WatermarkRegistry

        key = secrets.token_bytes(32)
        registry = WatermarkRegistry(secret_key=key)
        repr_str = repr(registry)

        # Should not contain the key bytes
        assert key.hex() not in repr_str
        assert "secret" not in repr_str.lower() or "WatermarkRegistry" in repr_str


# =============================================================================
# LOW SEVERITY TESTS
# =============================================================================


class TestCapabilityTTL:
    """Test reduced default capability TTL."""

    def test_default_ttl_is_15_minutes(self):
        """Default TTL should be 15 minutes, not 1 hour."""
        from our_privacy.capabilities import DEFAULT_TTL_SECONDS

        assert DEFAULT_TTL_SECONDS == 900  # 15 * 60

    def test_capability_uses_new_default(self):
        """New capabilities use the reduced TTL."""
        from our_privacy.capabilities import Capability

        cap = Capability.create(
            issuer_did="did:example:issuer",
            holder_did="did:example:holder",
            resource="test_resource",
            actions=["read"],
        )

        # Should expire in ~15 minutes, not 1 hour
        ttl = cap.ttl_seconds
        assert ttl <= 900
        assert ttl > 850  # Allow some clock drift


class TestCanaryStripWarning:
    """Test that strip_canaries has security documentation."""

    def test_docstring_contains_warning(self):
        """strip_canaries docstring contains security warning."""
        from our_privacy.canary import strip_canaries

        docstring = strip_canaries.__doc__
        assert "WARNING" in docstring or "\u26a0\ufe0f" in docstring
        assert "ADVERSARIAL" in docstring

    def test_strip_canaries_still_works(self):
        """Function still strips canaries despite warnings."""
        from our_privacy.canary import (
            CanaryToken,
            EmbeddingStrategy,
            embed_canary,
            strip_canaries,
        )

        secret = secrets.token_bytes(32)
        token = CanaryToken.generate(secret, "test_recipient")

        content = "This is test content."
        watermarked = embed_canary(content, token, EmbeddingStrategy.VISIBLE)

        # Verify canary is present
        assert "CANARY" in watermarked

        # Strip should remove it
        stripped = strip_canaries(watermarked)
        assert "CANARY" not in stripped


class TestExtractionRateLimiting:
    """Test per-DID rate limiting in ExtractionService."""

    def test_rate_limit_enforced(self):
        """Requests beyond limit are rejected."""
        from our_privacy.extraction import (
            ExtractionLevel,
            ExtractionService,
            RateLimitConfig,
            RateLimitExceededError,
        )

        # Very low limit for testing
        config = RateLimitConfig(max_extractions_per_window=2, window_seconds=3600)
        service = ExtractionService(rate_limit_config=config)

        # First two should succeed
        service.extract("content 1", ExtractionLevel.THEMES, "src_1", "did:test:alice")
        service.extract("content 2", ExtractionLevel.THEMES, "src_2", "did:test:alice")

        # Third should fail
        with pytest.raises(RateLimitExceededError) as exc_info:
            service.extract("content 3", ExtractionLevel.THEMES, "src_3", "did:test:alice")

        assert exc_info.value.did == "did:test:alice"
        assert exc_info.value.limit == 2

    def test_different_dids_have_separate_limits(self):
        """Different DIDs have independent rate limits."""
        from our_privacy.extraction import (
            ExtractionLevel,
            ExtractionService,
            RateLimitConfig,
            RateLimitExceededError,
        )

        config = RateLimitConfig(max_extractions_per_window=1, window_seconds=3600)
        service = ExtractionService(rate_limit_config=config)

        # Alice uses her limit
        service.extract("content 1", ExtractionLevel.THEMES, "src_1", "did:test:alice")

        # Bob can still extract
        service.extract("content 2", ExtractionLevel.THEMES, "src_2", "did:test:bob")

        # Alice is blocked
        with pytest.raises(RateLimitExceededError):
            service.extract("content 3", ExtractionLevel.THEMES, "src_3", "did:test:alice")

    def test_rate_limit_status_reporting(self):
        """Rate limit status is correctly reported."""
        from our_privacy.extraction import (
            ExtractionLevel,
            ExtractionService,
            RateLimitConfig,
        )

        config = RateLimitConfig(max_extractions_per_window=5, window_seconds=3600)
        service = ExtractionService(rate_limit_config=config)

        # Before any extractions
        status = service.get_rate_limit_status("did:test:alice")
        assert status["remaining"] == 5
        assert status["used"] == 0

        # After one extraction
        service.extract("content", ExtractionLevel.THEMES, "src", "did:test:alice")
        status = service.get_rate_limit_status("did:test:alice")
        assert status["remaining"] == 4
        assert status["used"] == 1

    def test_no_did_skips_rate_limit(self):
        """Requests without DID skip rate limiting."""
        from our_privacy.extraction import (
            ExtractionLevel,
            ExtractionService,
            RateLimitConfig,
        )

        config = RateLimitConfig(max_extractions_per_window=1, window_seconds=3600)
        service = ExtractionService(rate_limit_config=config)

        # Multiple requests without DID should all succeed
        for i in range(5):
            service.extract(f"content {i}", ExtractionLevel.THEMES, f"src_{i}")
