"""Tests for content filtering module."""

import pytest

from app.services.safety.content.filter import (
    ContentCategory,
    ContentFilter,
    FilterAction,
    FilterPattern,
    filter_content,
    scan_for_secrets,
)
from app.services.safety.exceptions import ContentFilterError


@pytest.fixture
def filter_all() -> ContentFilter:
    """Create a ContentFilter with all filters enabled."""
    return ContentFilter(
        enable_pii_filter=True,
        enable_secret_filter=True,
        enable_injection_filter=True,
    )


@pytest.fixture
def filter_pii_only() -> ContentFilter:
    """Create a ContentFilter with only PII filter."""
    return ContentFilter(
        enable_pii_filter=True,
        enable_secret_filter=False,
        enable_injection_filter=False,
    )


class TestContentFilter:
    """Tests for ContentFilter class."""

    @pytest.mark.asyncio
    async def test_filter_email(self, filter_pii_only: ContentFilter) -> None:
        """Test filtering email addresses."""
        content = "Contact me at john.doe@example.com for details."
        result = await filter_pii_only.filter(content)

        assert result.has_sensitive_content
        assert "[EMAIL]" in result.filtered_content
        assert "john.doe@example.com" not in result.filtered_content
        assert len(result.matches) == 1
        assert result.matches[0].category == ContentCategory.PII

    @pytest.mark.asyncio
    async def test_filter_phone_number(self, filter_pii_only: ContentFilter) -> None:
        """Test filtering phone numbers."""
        content = "Call me at 555-123-4567 or (555) 987-6543."
        result = await filter_pii_only.filter(content)

        assert result.has_sensitive_content
        assert "[PHONE]" in result.filtered_content
        # Should redact both phone numbers
        assert "555-123-4567" not in result.filtered_content

    @pytest.mark.asyncio
    async def test_filter_ssn(self, filter_pii_only: ContentFilter) -> None:
        """Test filtering Social Security Numbers."""
        content = "SSN: 123-45-6789"
        result = await filter_pii_only.filter(content)

        assert result.has_sensitive_content
        assert "[SSN]" in result.filtered_content
        assert "123-45-6789" not in result.filtered_content

    @pytest.mark.asyncio
    async def test_filter_credit_card(self, filter_all: ContentFilter) -> None:
        """Test filtering credit card numbers."""
        content = "Card: 4111-2222-3333-4444"
        result = await filter_all.filter(content)

        assert result.has_sensitive_content
        assert "[CREDIT_CARD]" in result.filtered_content

    @pytest.mark.asyncio
    async def test_block_api_key(self, filter_all: ContentFilter) -> None:
        """Test blocking API keys."""
        content = "api_key: sk-abcdef1234567890abcdef1234567890"
        result = await filter_all.filter(content)

        assert result.blocked
        assert result.block_reason is not None
        assert "api_key" in result.block_reason.lower()

    @pytest.mark.asyncio
    async def test_block_github_token(self, filter_all: ContentFilter) -> None:
        """Test blocking GitHub tokens."""
        content = "token: ghp_abcdefghijklmnopqrstuvwxyz1234567890"
        result = await filter_all.filter(content)

        assert result.blocked
        assert len(result.matches) > 0

    @pytest.mark.asyncio
    async def test_block_private_key(self, filter_all: ContentFilter) -> None:
        """Test blocking private keys."""
        content = """
        -----BEGIN RSA PRIVATE KEY-----
        MIIEpAIBAAKCAQEA...
        -----END RSA PRIVATE KEY-----
        """
        result = await filter_all.filter(content)

        assert result.blocked

    @pytest.mark.asyncio
    async def test_block_password_in_url(self, filter_all: ContentFilter) -> None:
        """Test blocking passwords in URLs."""
        content = "Connect to: postgres://user:secretpassword@localhost/db"
        result = await filter_all.filter(content)

        assert result.blocked or result.has_sensitive_content

    @pytest.mark.asyncio
    async def test_warn_ip_address(self, filter_pii_only: ContentFilter) -> None:
        """Test warning on IP addresses."""
        content = "Server IP: 192.168.1.100"
        result = await filter_pii_only.filter(content)

        # IP addresses generate warnings, not blocks
        assert len(result.warnings) > 0 or result.has_sensitive_content

    @pytest.mark.asyncio
    async def test_no_false_positives_clean_content(
        self,
        filter_all: ContentFilter,
    ) -> None:
        """Test that clean content passes through."""
        content = "This is a normal message with no sensitive data."
        result = await filter_all.filter(content)

        assert not result.blocked
        assert result.filtered_content == content

    @pytest.mark.asyncio
    async def test_raise_on_block(self, filter_all: ContentFilter) -> None:
        """Test raising exception on blocked content."""
        content = "-----BEGIN RSA PRIVATE KEY-----"

        with pytest.raises(ContentFilterError):
            await filter_all.filter(content, raise_on_block=True)


class TestFilterDict:
    """Tests for dictionary filtering."""

    @pytest.mark.asyncio
    async def test_filter_dict_values(self, filter_pii_only: ContentFilter) -> None:
        """Test filtering string values in a dictionary."""
        data = {
            "name": "John Doe",
            "email": "john@example.com",
            "age": 30,
        }
        result = await filter_pii_only.filter_dict(data)

        assert "[EMAIL]" in result["email"]
        assert result["age"] == 30  # Non-string unchanged

    @pytest.mark.asyncio
    async def test_filter_dict_recursive(
        self,
        filter_pii_only: ContentFilter,
    ) -> None:
        """Test recursive dictionary filtering."""
        data = {
            "user": {
                "contact": {
                    "email": "test@example.com",
                }
            }
        }
        result = await filter_pii_only.filter_dict(data, recursive=True)

        assert "[EMAIL]" in result["user"]["contact"]["email"]

    @pytest.mark.asyncio
    async def test_filter_dict_specific_keys(
        self,
        filter_pii_only: ContentFilter,
    ) -> None:
        """Test filtering specific keys only."""
        data = {
            "public_email": "public@example.com",
            "private_email": "private@example.com",
        }
        result = await filter_pii_only.filter_dict(
            data,
            keys_to_filter=["private_email"],
        )

        # Only private_email should be filtered
        assert "public@example.com" in result["public_email"]
        assert "[EMAIL]" in result["private_email"]


class TestScan:
    """Tests for content scanning."""

    @pytest.mark.asyncio
    async def test_scan_without_filtering(
        self,
        filter_all: ContentFilter,
    ) -> None:
        """Test scanning without modifying content."""
        content = "Email: test@example.com, SSN: 123-45-6789"
        matches = await filter_all.scan(content)

        assert len(matches) >= 2

    @pytest.mark.asyncio
    async def test_scan_specific_categories(
        self,
        filter_all: ContentFilter,
    ) -> None:
        """Test scanning for specific categories only."""
        content = (
            "Email: test@example.com, token: ghp_abc123456789012345678901234567890123"
        )

        # Scan only for secrets
        matches = await filter_all.scan(
            content,
            categories=[ContentCategory.SECRETS],
        )

        # Should only find the token, not the email
        assert all(m.category == ContentCategory.SECRETS for m in matches)


class TestValidateSafe:
    """Tests for safe validation."""

    @pytest.mark.asyncio
    async def test_validate_safe_clean(self, filter_all: ContentFilter) -> None:
        """Test validation of clean content."""
        content = "This is safe content."
        is_safe, issues = await filter_all.validate_safe(content)

        assert is_safe is True
        assert len(issues) == 0

    @pytest.mark.asyncio
    async def test_validate_safe_with_secrets(
        self,
        filter_all: ContentFilter,
    ) -> None:
        """Test validation of content with secrets."""
        content = "-----BEGIN RSA PRIVATE KEY-----"
        is_safe, issues = await filter_all.validate_safe(content)

        assert is_safe is False
        assert len(issues) > 0


class TestCustomPatterns:
    """Tests for custom pattern support."""

    @pytest.mark.asyncio
    async def test_add_custom_pattern(self) -> None:
        """Test adding a custom filter pattern."""
        content_filter = ContentFilter(
            enable_pii_filter=False,
            enable_secret_filter=False,
            enable_injection_filter=False,
        )

        # Add custom pattern for internal IDs
        content_filter.add_pattern(
            FilterPattern(
                name="internal_id",
                category=ContentCategory.CUSTOM,
                pattern=r"INTERNAL-[A-Z0-9]{8}",
                action=FilterAction.REDACT,
                replacement="[INTERNAL_ID]",
            )
        )

        content = "Reference: INTERNAL-ABC12345"
        result = await content_filter.filter(content)

        assert "[INTERNAL_ID]" in result.filtered_content

    @pytest.mark.asyncio
    async def test_disable_pattern(self, filter_pii_only: ContentFilter) -> None:
        """Test disabling a pattern."""
        filter_pii_only.enable_pattern("email", enabled=False)

        content = "Email: test@example.com"
        result = await filter_pii_only.filter(content)

        # Email should not be filtered
        assert "test@example.com" in result.filtered_content

    @pytest.mark.asyncio
    async def test_remove_pattern(self, filter_pii_only: ContentFilter) -> None:
        """Test removing a pattern."""
        removed = filter_pii_only.remove_pattern("email")
        assert removed is True

        content = "Email: test@example.com"
        result = await filter_pii_only.filter(content)

        # Email should not be filtered
        assert "test@example.com" in result.filtered_content


class TestPatternStats:
    """Tests for pattern statistics."""

    def test_get_pattern_stats(self, filter_all: ContentFilter) -> None:
        """Test getting pattern statistics."""
        stats = filter_all.get_pattern_stats()

        assert stats["total_patterns"] > 0
        assert "by_category" in stats
        assert "by_action" in stats


class TestConvenienceFunctions:
    """Tests for convenience functions."""

    @pytest.mark.asyncio
    async def test_filter_content_function(self) -> None:
        """Test the quick filter function."""
        content = "Email: test@example.com"
        filtered = await filter_content(content)

        assert "test@example.com" not in filtered

    @pytest.mark.asyncio
    async def test_scan_for_secrets_function(self) -> None:
        """Test the quick secret scan function."""
        content = "Token: ghp_abcdefghijklmnopqrstuvwxyz1234567890"
        matches = await scan_for_secrets(content)

        assert len(matches) > 0
        assert matches[0].category in (
            ContentCategory.SECRETS,
            ContentCategory.CREDENTIALS,
        )
