import pytest
from wc32 import encode, decode


class TestBasicEncodeDecode:
    def test_encode_decode_roundtrip(self):
        text = "Hello, World!"
        key = "secret_key"
        encoded = encode(text, key)
        decoded = decode(encoded, key)
        assert decoded == text

    def test_encode_decode_empty_string(self):
        text = ""
        key = "secret_key"
        encoded = encode(text, key)
        decoded = decode(encoded, key)
        assert decoded == text

    def test_encode_decode_unicode(self):
        text = "Hello 你好 مرحبا"
        key = "secret_key"
        encoded = encode(text, key)
        decoded = decode(encoded, key)
        assert decoded == text

    def test_different_keys_produce_different_output(self):
        text = "Hello"
        encoded1 = encode(text, "key1")
        encoded2 = encode(text, "key2")
        assert encoded1 != encoded2


class TestSalt:
    def test_salt_generates_different_output(self):
        text = "Hello"
        key = "secret_key"
        encoded1 = encode(text, key, salt=True)
        encoded2 = encode(text, key, salt=True)
        assert encoded1 != encoded2

    def test_salt_roundtrip(self):
        text = "Hello, World!"
        key = "secret_key"
        encoded = encode(text, key, salt=True)
        decoded = decode(encoded, key)
        assert decoded == text

    def test_custom_salt(self):
        text = "Hello"
        key = "secret_key"
        custom_salt = "deadbeef"  # Must be valid hex
        encoded = encode(text, key, salt=custom_salt)
        decoded = decode(encoded, key)
        assert decoded == text
        assert encoded.startswith(custom_salt + ".")

    def test_deterministic_without_salt(self):
        text = "Hello"
        key = "secret_key"
        encoded1 = encode(text, key, salt=False)
        encoded2 = encode(text, key, salt=False)
        assert encoded1 == encoded2


class TestErrorHandling:
    def test_empty_key_raises_error(self):
        with pytest.raises(ValueError, match="Key must not be empty"):
            encode("text", "")

    def test_wrong_key_produces_different_output(self):
        text = "Hello"
        key = "correct_key"
        encoded = encode(text, key)
        decoded_wrong = decode(encoded, "wrong_key")
        assert decoded_wrong != text

    def test_invalid_text_type_raises_error(self):
        with pytest.raises(TypeError, match="Text must be a string"):
            encode(123, "key")

    def test_invalid_key_type_raises_error(self):
        with pytest.raises(TypeError, match="Key must be a string"):
            encode("text", 123)

    def test_invalid_encoded_type_raises_error(self):
        with pytest.raises(TypeError, match="Encoded text must be a string"):
            decode(123, "key")

    def test_corrupted_data_raises_error(self):
        with pytest.raises(ValueError, match="Invalid character"):
            decode("invalid!!!data", "key")

    def test_invalid_salt_type_raises_error(self):
        with pytest.raises(TypeError, match="Salt must be"):
            encode("text", "key", salt=123)


class TestEdgeCases:
    def test_long_text(self):
        text = "a" * 1000
        key = "secret_key"
        encoded = encode(text, key)
        decoded = decode(encoded, key)
        assert decoded == text

    def test_special_characters(self):
        text = "!@#$%^&*()_+-=[]{}|;':\",./<>?"
        key = "secret_key"
        encoded = encode(text, key)
        decoded = decode(encoded, key)
        assert decoded == text

    def test_whitespace(self):
        text = "   spaces   \n\tnewlines"
        key = "secret_key"
        encoded = encode(text, key)
        decoded = decode(encoded, key)
        assert decoded == text
