"""
Extended tests for BrowseCompAnswerDecoder - Answer decoding pipeline.

Tests cover:
- Decoder initialization
- Base64 decoding
- Hex decoding
- URL encoding decoding
- ROT13 decoding
- Caesar cipher decoding
- Plaintext detection
- Validation logic
- Edge cases and error handling
"""

import base64
import urllib.parse


class TestDecoderInitialization:
    """Tests for BrowseCompAnswerDecoder initialization."""

    def test_encoding_schemes_list(self):
        """Decoder should have expected encoding schemes."""
        encoding_schemes = [
            "base64",
            "hex",
            "url_encoding",
            "rot13",
            "caesar_cipher",
        ]

        assert len(encoding_schemes) == 5
        assert "base64" in encoding_schemes
        assert "rot13" in encoding_schemes

    def test_encoded_patterns_list(self):
        """Decoder should have encoded patterns for detection."""
        encoded_patterns = [
            r"^[A-Za-z0-9+/]+=*$",  # Base64 pattern
            r"^[0-9A-Fa-f]+$",  # Hex pattern
            r"%[0-9A-Fa-f]{2}",  # URL encoded
            r"^[A-Za-z0-9]{8,}$",  # Random string pattern
        ]

        assert len(encoded_patterns) == 4

    def test_patterns_are_valid_regex(self):
        """All patterns should be valid regex."""
        import re

        patterns = [
            r"^[A-Za-z0-9+/]+=*$",
            r"^[0-9A-Fa-f]+$",
            r"%[0-9A-Fa-f]{2}",
        ]

        for pattern in patterns:
            # Should not raise
            compiled = re.compile(pattern)
            assert compiled is not None


class TestBase64Decoding:
    """Tests for base64 decoding."""

    def test_decode_valid_base64(self):
        """Should decode valid base64 string."""
        original = "Hello World"
        encoded = base64.b64encode(original.encode()).decode()

        decoded_bytes = base64.b64decode(encoded)
        decoded = decoded_bytes.decode("utf-8")

        assert decoded == "Hello World"

    def test_decode_base64_with_padding(self):
        """Should handle base64 with padding."""
        # "Test" encodes to "VGVzdA==" (with padding)
        encoded = "VGVzdA=="
        decoded = base64.b64decode(encoded).decode("utf-8")

        assert decoded == "Test"

    def test_decode_base64_missing_padding(self):
        """Should add missing padding before decoding."""
        encoded = "VGVzdA"  # Missing == padding
        missing_padding = len(encoded) % 4
        if missing_padding:
            encoded += "=" * (4 - missing_padding)

        decoded = base64.b64decode(encoded).decode("utf-8")
        assert decoded == "Test"

    def test_invalid_base64_returns_none(self):
        """Invalid base64 should return None."""
        encoded = "not valid base64!!!"

        try:
            base64.b64decode(encoded)
            decoded = "success"
        except Exception:
            decoded = None

        assert decoded is None


class TestHexDecoding:
    """Tests for hexadecimal decoding."""

    def test_decode_valid_hex(self):
        """Should decode valid hex string."""
        original = "Hello"
        hex_encoded = original.encode().hex()

        decoded = bytes.fromhex(hex_encoded).decode("utf-8")

        assert decoded == "Hello"

    def test_decode_hex_uppercase(self):
        """Should decode uppercase hex."""
        hex_str = "48454C4C4F"  # "HELLO" in hex

        decoded = bytes.fromhex(hex_str).decode("utf-8")

        assert decoded == "HELLO"

    def test_decode_hex_lowercase(self):
        """Should decode lowercase hex."""
        hex_str = "68656c6c6f"  # "hello" in hex

        decoded = bytes.fromhex(hex_str).decode("utf-8")

        assert decoded == "hello"

    def test_odd_length_hex_returns_none(self):
        """Odd length hex should fail."""
        hex_str = "48454C4C4"  # Odd length

        try:
            if len(hex_str) % 2 != 0:
                raise ValueError("Odd length hex")
            decoded = bytes.fromhex(hex_str).decode("utf-8")
        except Exception:
            decoded = None

        assert decoded is None


class TestURLDecoding:
    """Tests for URL encoding decoding."""

    def test_decode_url_encoded_string(self):
        """Should decode URL encoded string."""
        encoded = "Hello%20World"

        decoded = urllib.parse.unquote(encoded)

        assert decoded == "Hello World"

    def test_decode_special_characters(self):
        """Should decode special URL characters."""
        encoded = "%3Cscript%3E"

        decoded = urllib.parse.unquote(encoded)

        assert decoded == "<script>"

    def test_decode_unicode_url_encoding(self):
        """Should decode unicode URL encoding."""
        encoded = "%C3%A9"  # é in UTF-8 URL encoding

        decoded = urllib.parse.unquote(encoded)

        assert decoded == "é"

    def test_decode_already_decoded_string(self):
        """Decoding already decoded string should return same."""
        original = "Hello World"

        decoded = urllib.parse.unquote(original)

        assert decoded == "Hello World"


class TestROT13Decoding:
    """Tests for ROT13 decoding."""

    def test_decode_rot13(self):
        """Should decode ROT13 encoded text."""
        import codecs

        # ROT13 of "Hello" is "Uryyb"
        encoded = "Uryyb"

        decoded = codecs.decode(encoded, "rot13")

        assert decoded == "Hello"

    def test_rot13_is_symmetric(self):
        """ROT13 applied twice should return original."""
        import codecs

        original = "TestString"

        encoded = codecs.encode(original, "rot13")
        decoded = codecs.decode(encoded, "rot13")

        assert decoded == original

    def test_rot13_preserves_case(self):
        """ROT13 should preserve character case."""
        import codecs

        original = "AbCdEf"
        encoded = codecs.encode(original, "rot13")
        decoded = codecs.decode(encoded, "rot13")

        assert decoded == original

    def test_rot13_ignores_non_alpha(self):
        """ROT13 should ignore non-alphabetic characters."""
        import codecs

        original = "Hello, World! 123"
        encoded = codecs.encode(original, "rot13")
        decoded = codecs.decode(encoded, "rot13")

        assert decoded == original


class TestCaesarCipher:
    """Tests for Caesar cipher decoding."""

    def test_caesar_shift_basic(self):
        """Should apply Caesar shift correctly."""

        def caesar_shift(text, shift):
            result = []
            for char in text:
                if char.isalpha():
                    start = ord("A") if char.isupper() else ord("a")
                    shifted = (ord(char) - start + shift) % 26 + start
                    result.append(chr(shifted))
                else:
                    result.append(char)
            return "".join(result)

        # Shift "abc" by 1 should give "bcd"
        result = caesar_shift("abc", 1)
        assert result == "bcd"

    def test_caesar_shift_wraparound(self):
        """Caesar shift should wrap around alphabet."""

        def caesar_shift(text, shift):
            result = []
            for char in text:
                if char.isalpha():
                    start = ord("A") if char.isupper() else ord("a")
                    shifted = (ord(char) - start + shift) % 26 + start
                    result.append(chr(shifted))
                else:
                    result.append(char)
            return "".join(result)

        # Shift "xyz" by 3 should give "abc"
        result = caesar_shift("xyz", 3)
        assert result == "abc"

    def test_caesar_preserves_case(self):
        """Caesar cipher should preserve character case."""

        def caesar_shift(text, shift):
            result = []
            for char in text:
                if char.isalpha():
                    start = ord("A") if char.isupper() else ord("a")
                    shifted = (ord(char) - start + shift) % 26 + start
                    result.append(chr(shifted))
                else:
                    result.append(char)
            return "".join(result)

        result = caesar_shift("AbC", 1)
        assert result == "BcD"

    def test_caesar_ignores_non_alpha(self):
        """Caesar cipher should ignore non-alphabetic characters."""

        def caesar_shift(text, shift):
            result = []
            for char in text:
                if char.isalpha():
                    start = ord("A") if char.isupper() else ord("a")
                    shifted = (ord(char) - start + shift) % 26 + start
                    result.append(chr(shifted))
                else:
                    result.append(char)
            return "".join(result)

        result = caesar_shift("a1b2c", 1)
        assert result == "b1c2d"


class TestPlaintextDetection:
    """Tests for plaintext detection."""

    def test_short_string_is_plaintext(self):
        """Very short strings should be plaintext."""
        answer = "Yes"

        is_plaintext = len(answer) < 4
        assert is_plaintext is True

    def test_english_words_detected(self):
        """Strings with English words should be plaintext."""
        answer = "The quick brown fox"
        english_indicators = ["the", "and", "of", "in", "to"]

        has_english = any(word in answer.lower() for word in english_indicators)
        assert has_english is True

    def test_multi_word_is_plaintext(self):
        """Multi-word strings with spaces should be plaintext."""
        answer = "Hello World"

        has_spaces = " " in answer
        has_multiple_words = len(answer.split()) > 1

        assert has_spaces is True
        assert has_multiple_words is True

    def test_year_format_is_plaintext(self):
        """Year format should be plaintext."""
        import re

        answer = "2024"
        year_pattern = r"^\d{4}$"

        is_year = bool(re.match(year_pattern, answer))
        assert is_year is True

    def test_name_format_is_plaintext(self):
        """Name format should be plaintext."""
        import re

        answer = "John Smith"
        name_pattern = r"^[A-Z][a-z]+ [A-Z][a-z]+$"

        is_name = bool(re.match(name_pattern, answer))
        assert is_name is True

    def test_percentage_format_is_plaintext(self):
        """Percentage format should be plaintext."""
        import re

        answer = "85%"
        percentage_pattern = r"^\d+%$"

        is_percentage = bool(re.match(percentage_pattern, answer))
        assert is_percentage is True


class TestAnswerValidation:
    """Tests for decoded answer validation."""

    def test_empty_string_invalid(self):
        """Empty string should be invalid."""
        decoded = ""

        is_valid = len(decoded.strip()) > 0 if decoded else False
        assert is_valid is False

    def test_too_long_string_invalid(self):
        """String over 1000 chars should be invalid."""
        decoded = "x" * 1001

        is_valid = len(decoded) <= 1000
        assert is_valid is False

    def test_valid_length_string(self):
        """Reasonable length string should be valid."""
        decoded = "This is a valid answer"

        is_valid = 1 <= len(decoded) <= 1000
        assert is_valid is True

    def test_printable_characters_required(self):
        """Should require mostly printable characters."""
        decoded = "Hello World"

        printable_count = sum(1 for c in decoded if c.isprintable())
        ratio = printable_count / len(decoded) if decoded else 0
        is_valid = ratio >= 0.8

        assert is_valid is True

    def test_control_characters_invalid(self):
        """Control characters should make string invalid."""
        decoded = "Hello\x00World"  # Contains null byte

        has_control = any(ord(c) < 32 and c not in "\t\n\r" for c in decoded)
        assert has_control is True

    def test_needs_some_letters(self):
        """Should require some alphabetic characters."""
        decoded = "Hello World"

        alpha_count = sum(1 for c in decoded if c.isalpha())
        ratio = alpha_count / len(decoded) if decoded else 0
        has_letters = ratio >= 0.3

        assert has_letters is True

    def test_too_much_punctuation_invalid(self):
        """Too much punctuation should be invalid."""
        decoded = "...!!???###"

        punct_count = sum(
            1 for c in decoded if not c.isalnum() and not c.isspace()
        )
        ratio = punct_count / len(decoded) if decoded else 0
        too_much_punct = ratio > 0.5

        assert too_much_punct is True


class TestEnglishScoring:
    """Tests for English-likeness scoring."""

    def test_common_letters_score_higher(self):
        """Common English letters should score higher."""
        common_letters = "etaoinshrdlcumwfgypbvkjxqz"

        # 'e' is most common
        assert common_letters.index("e") < common_letters.index("z")

    def test_common_words_add_bonus(self):
        """Common English words should add bonus to score."""
        text = "the quick brown fox"
        common_words = ["the", "and", "of", "to", "a", "in"]

        word_bonus = sum(1 for word in common_words if word in text.lower())
        assert word_bonus >= 1  # "the" is present

    def test_empty_text_scores_zero(self):
        """Empty text should score zero."""
        text = ""

        if not text:
            score = 0.0
        else:
            score = 1.0

        assert score == 0.0


class TestDecodeAnswer:
    """Tests for the main decode_answer method."""

    def test_empty_input_returns_original(self):
        """Empty input should return original."""
        raw_answer = ""

        if not raw_answer or len(raw_answer.strip()) == 0:
            result = (raw_answer, None)
        else:
            result = ("decoded", "scheme")

        assert result == ("", None)

    def test_plaintext_returns_original(self):
        """Plaintext answer should return original with None scheme."""
        raw_answer = "The answer is 42"

        # Simulating is_likely_direct_answer check
        is_plaintext = " " in raw_answer and len(raw_answer.split()) > 1
        if is_plaintext:
            result = (raw_answer, None)
        else:
            result = ("decoded", "scheme")

        assert result == ("The answer is 42", None)

    def test_decoding_returns_scheme_used(self):
        """Successful decoding should return scheme used."""
        raw_answer = "VGVzdA=="  # "Test" in base64

        # Simulate successful base64 decode
        try:
            decoded = base64.b64decode(raw_answer).decode("utf-8")
            result = (decoded, "base64")
        except Exception:
            result = (raw_answer, None)

        assert result == ("Test", "base64")


class TestAnalyzeAnswerEncoding:
    """Tests for encoding analysis."""

    def test_analysis_includes_original(self):
        """Analysis should include original answer."""
        answer = "TestAnswer"

        analysis = {
            "original": answer,
            "length": len(answer),
        }

        assert analysis["original"] == "TestAnswer"
        assert analysis["length"] == 10

    def test_analysis_includes_pattern_matches(self):
        """Analysis should include pattern matches."""
        import re

        answer = "VGVzdA=="
        patterns = [
            (r"^[A-Za-z0-9+/]+=*$", "base64"),
            (r"^[0-9A-Fa-f]+$", "hex"),
        ]

        matches = []
        for pattern, name in patterns:
            if re.search(pattern, answer):
                matches.append(name)

        assert "base64" in matches

    def test_analysis_tracks_attempted_decodings(self):
        """Analysis should track all decoding attempts."""
        schemes = ["base64", "hex", "rot13"]
        attempted = {}

        for scheme in schemes:
            attempted[scheme] = {"decoded": None, "valid": False}

        assert len(attempted) == 3
        assert "base64" in attempted


class TestEdgeCases:
    """Tests for edge cases."""

    def test_whitespace_only_input(self):
        """Whitespace-only input should be handled."""
        raw_answer = "   "

        if not raw_answer or len(raw_answer.strip()) == 0:
            result = (raw_answer, None)
        else:
            result = ("decoded", "scheme")

        assert result[1] is None

    def test_unicode_input_handled(self):
        """Unicode input should be handled."""
        raw_answer = "café résumé"

        # Should be detected as plaintext
        is_plaintext = " " in raw_answer
        assert is_plaintext is True

    def test_mixed_encoding_patterns(self):
        """Mixed encoding patterns should be analyzed."""
        answer = "Hello%20VGVzdA=="

        has_url_encoding = "%" in answer
        has_base64_chars = "=" in answer

        assert has_url_encoding is True
        assert has_base64_chars is True

    def test_very_long_encoded_string(self):
        """Very long encoded strings should be handled."""
        # 1000 character base64 would be ~750 bytes decoded
        long_string = "A" * 1000

        # Should still attempt decoding
        assert len(long_string) <= 1001
