"""Tests for profanity service.

Unit tests for the profanity filtering service that validates
user-generated content like display names.
"""

from app.services.profanity_service import (
    censor_text,
    contains_profanity,
    validate_display_name,
    validate_text,
)


class TestContainsProfanity:
    """Tests for the contains_profanity function."""

    def test_clean_text_returns_false(self) -> None:
        """
        Test that clean text is not flagged as profanity.

        Normal usernames and display names should pass validation
        without being incorrectly flagged.
        """
        assert contains_profanity("PlayerOne") is False
        assert contains_profanity("CoolGamer123") is False
        assert contains_profanity("DragonMaster") is False

    def test_profane_text_returns_true(self) -> None:
        """
        Test that profane text is correctly detected.

        Common profanity should be detected to prevent inappropriate
        usernames from being created.
        """
        # Using a word that's definitely in the default word list
        assert contains_profanity("shit") is True
        assert contains_profanity("fuck") is True

    def test_mixed_case_profanity_detected(self) -> None:
        """
        Test that profanity is detected regardless of case.

        Users may try to bypass filters using mixed case, which
        should still be caught.
        """
        assert contains_profanity("SHIT") is True
        assert contains_profanity("ShIt") is True

    def test_empty_string_is_clean(self) -> None:
        """
        Test that empty strings are not flagged.

        Empty strings don't contain profanity by definition.
        """
        assert contains_profanity("") is False

    def test_profanity_with_number_suffix_detected(self) -> None:
        """
        Test that profanity followed by numbers is detected.

        Users commonly try to bypass filters by appending numbers
        to bad words (e.g., "shit123"). The filter should catch these.
        """
        assert contains_profanity("shit123") is True
        assert contains_profanity("fuck2024") is True
        assert contains_profanity("ass99") is True

    def test_profanity_with_number_prefix_detected(self) -> None:
        """
        Test that profanity preceded by numbers is detected.

        Numbers before bad words should also be caught.
        """
        assert contains_profanity("123shit") is True
        assert contains_profanity("69fuck") is True

    def test_leet_speak_profanity_detected(self) -> None:
        """
        Test that leet-speak substitutions are detected.

        Common character substitutions (@ for a, 1 for i, etc.)
        should be normalized and caught by the filter.
        """
        assert contains_profanity("sh1t") is True
        assert contains_profanity("f@ck") is True
        assert contains_profanity("a$$") is True
        assert contains_profanity("$hit") is True

    def test_profanity_with_separators_detected(self) -> None:
        """
        Test that profanity broken up with separators is detected.

        Users may insert dots, dashes, or underscores between letters
        to break up bad words. The filter should remove these and check.
        """
        assert contains_profanity("s.h.i.t") is True
        assert contains_profanity("f-u-c-k") is True
        assert contains_profanity("s_h_i_t") is True

    def test_clean_words_with_numbers_not_flagged(self) -> None:
        """
        Test that clean words with numbers are not incorrectly flagged.

        Normal gaming-style names with numbers should pass validation.
        """
        assert contains_profanity("Player123") is False
        assert contains_profanity("Gamer2024") is False
        assert contains_profanity("Dragon99") is False
        assert contains_profanity("FireMage42") is False


class TestValidateDisplayName:
    """Tests for the validate_display_name function."""

    def test_valid_name_returns_success(self) -> None:
        """
        Test that valid display names pass validation.

        Normal display names should return (True, None) indicating
        they are valid with no error message.
        """
        is_valid, error = validate_display_name("GoodPlayer")
        assert is_valid is True
        assert error is None

    def test_profane_name_returns_error(self) -> None:
        """
        Test that profane display names return an error.

        Display names containing profanity should return (False, error)
        with a user-friendly error message.
        """
        is_valid, error = validate_display_name("shit123")
        assert is_valid is False
        assert error is not None
        assert "inappropriate" in error.lower()

    def test_error_message_is_user_friendly(self) -> None:
        """
        Test that error messages are suitable for display to users.

        The error message should be professional and not contain
        the actual profanity.
        """
        is_valid, error = validate_display_name("fuckface")
        assert is_valid is False
        assert error == "Display name contains inappropriate language"


class TestValidateText:
    """Tests for the generic validate_text function."""

    def test_valid_text_passes(self) -> None:
        """
        Test that clean text passes validation.

        Generic text validation should work the same as display name
        validation for clean content.
        """
        is_valid, error = validate_text("This is a clean message", "message")
        assert is_valid is True
        assert error is None

    def test_custom_field_name_in_error(self) -> None:
        """
        Test that the field name appears in error messages.

        The error message should reference the specific field that
        failed validation for clarity.
        """
        is_valid, error = validate_text("shit", "bio")
        assert is_valid is False
        assert "Bio" in error  # Capitalized field name


class TestCensorText:
    """Tests for the censor_text function."""

    def test_profanity_is_censored(self) -> None:
        """
        Test that profanity is replaced with asterisks.

        The censor function should replace bad words while keeping
        the rest of the text intact.
        """
        result = censor_text("what the shit")
        assert "shit" not in result
        assert "****" in result

    def test_clean_text_unchanged(self) -> None:
        """
        Test that clean text is returned unchanged.

        Text without profanity should pass through the censor
        function without modification.
        """
        original = "This is a clean message"
        result = censor_text(original)
        assert result == original

    def test_custom_censor_char(self) -> None:
        """
        Test that custom censor characters are used.

        Users should be able to specify alternative censor
        characters for different display contexts.
        """
        result = censor_text("what the shit", censor_char="#")
        assert "#" in result
        assert "*" not in result
