"""Tests for encryption utilities."""

import base64

import pytest
from pydantic import SecretStr

from phoenix.server.encryption import EncryptionService, is_encrypted


class TestEncryptionService:
    """Tests for EncryptionService class."""

    def test_encrypt_decrypt_roundtrip(self) -> None:
        """Test encryption and decryption round trip."""
        service = EncryptionService(secret=SecretStr("test-secret-12345"))

        data = b"sensitive-api-key-12345"
        ciphertext = service.encrypt(data)

        assert isinstance(ciphertext, bytes)
        assert ciphertext != data  # Encrypted data is different

        decrypted = service.decrypt(ciphertext)
        assert decrypted == data

    def test_encrypt_empty_bytes_raises(self) -> None:
        """Test that encrypting empty bytes raises error."""
        service = EncryptionService(secret=SecretStr("test-secret"))

        with pytest.raises(ValueError, match="Cannot encrypt empty bytes"):
            service.encrypt(b"")

    def test_decrypt_empty_bytes_raises(self) -> None:
        """Test that decrypting empty bytes raises error."""
        service = EncryptionService(secret=SecretStr("test-secret"))

        with pytest.raises(ValueError, match="Cannot decrypt empty bytes"):
            service.decrypt(b"")

    def test_decrypt_with_wrong_secret_raises(self) -> None:
        """Test that decrypting with wrong secret raises error."""
        service1 = EncryptionService(secret=SecretStr("secret-one"))
        service2 = EncryptionService(secret=SecretStr("secret-two"))

        data = b"secret"
        ciphertext = service1.encrypt(data)

        with pytest.raises(ValueError, match="Decryption failed"):
            service2.decrypt(ciphertext)

    def test_decrypt_corrupted_data_raises(self) -> None:
        """Test that decrypting corrupted data raises error."""
        service = EncryptionService(secret=SecretStr("test-secret"))

        with pytest.raises(ValueError, match="Decryption failed"):
            service.decrypt(b"corrupted-data")

    def test_encrypt_unicode_characters(self) -> None:
        """Test encryption handles unicode characters."""
        service = EncryptionService(secret=SecretStr("test-secret"))

        data = "Hello 世界 🔐 émojis".encode("utf-8")
        ciphertext = service.encrypt(data)
        decrypted = service.decrypt(ciphertext)

        assert decrypted == data

    def test_encrypt_long_data(self) -> None:
        """Test encryption handles long data."""
        service = EncryptionService(secret=SecretStr("test-secret"))

        data = b"a" * 10000
        ciphertext = service.encrypt(data)
        decrypted = service.decrypt(ciphertext)

        assert decrypted == data

    def test_key_derivation_is_deterministic(self) -> None:
        """Test that the same secret always produces the same encryption key."""
        secret = SecretStr("test-secret-12345")

        service1 = EncryptionService(secret=secret)
        service2 = EncryptionService(secret=secret)

        data = b"test"
        ciphertext1 = service1.encrypt(data)
        ciphertext2 = service2.encrypt(data)

        # Both services should be able to decrypt each other's ciphertexts
        assert service1.decrypt(ciphertext2) == data
        assert service2.decrypt(ciphertext1) == data

    def test_encrypting_same_data_twice_produces_different_ciphertexts(self) -> None:
        """Test that encrypting the same data twice produces different ciphertexts."""
        service = EncryptionService(secret=SecretStr("test-secret"))

        data = b"sensitive-api-key-12345"
        ciphertext1 = service.encrypt(data)
        ciphertext2 = service.encrypt(data)

        # Ciphertexts should be different (due to random IV in Fernet)
        assert ciphertext1 != ciphertext2

        # But both should decrypt to the same data
        assert service.decrypt(ciphertext1) == data
        assert service.decrypt(ciphertext2) == data

    def test_encrypt_very_short_data(self) -> None:
        """Test encryption handles very short data (1 byte)."""
        service = EncryptionService(secret=SecretStr("test-secret"))
        data = b"a"
        ciphertext = service.encrypt(data)
        assert service.decrypt(ciphertext) == data


class TestIsEncrypted:
    """Tests for is_encrypted function."""

    def test_returns_true_for_valid_fernet_token(self) -> None:
        """Test that is_encrypted returns True for valid Fernet tokens."""
        service = EncryptionService(secret=SecretStr("test-secret"))
        ciphertext = service.encrypt(b"sensitive-data")

        assert is_encrypted(ciphertext) is True

    def test_returns_false_for_empty_bytes(self) -> None:
        """Test that is_encrypted returns False for empty bytes."""
        assert is_encrypted(b"") is False

    @pytest.mark.parametrize(
        "blob",
        [
            b"not-valid-base64!!!",  # Invalid base64 characters
            b"invalid\x00bytes",  # Binary data with null bytes
        ],
    )
    def test_returns_false_for_non_base64(self, blob: bytes) -> None:
        """Test that is_encrypted returns False for non-base64 data."""
        assert is_encrypted(blob) is False

    def test_returns_false_for_too_short_base64(self) -> None:
        """Test that is_encrypted returns False for base64 that's too short."""
        # Valid base64 that decodes to less than 57 bytes (minimum Fernet token size)
        short_base64 = base64.urlsafe_b64encode(b"short")
        assert is_encrypted(short_base64) is False

    def test_returns_false_for_exactly_56_bytes(self) -> None:
        """Test boundary case: base64 that decodes to exactly 56 bytes (one byte short)."""
        # Create base64 that decodes to exactly 56 bytes
        data_56_bytes = b"\x80" + b"\x00" * 55  # Correct version byte but too short
        base64_56 = base64.urlsafe_b64encode(data_56_bytes)
        assert is_encrypted(base64_56) is False

    def test_returns_true_for_exactly_57_bytes_with_correct_version(self) -> None:
        """Test boundary case: base64 that decodes to exactly 57 bytes (minimum valid size)."""
        # Create base64 that decodes to exactly 57 bytes with correct version byte
        data_57_bytes = b"\x80" + b"\x00" * 56  # Correct version byte, minimum valid size
        base64_57 = base64.urlsafe_b64encode(data_57_bytes)
        assert is_encrypted(base64_57) is True

    def test_returns_false_for_wrong_version_byte(self) -> None:
        """Test that is_encrypted returns False for base64 with wrong version byte."""
        # Base64 data that's long enough but has wrong version byte (0x00 instead of 0x80)
        fake_token = b"\x00" + b"\x00" * 56
        fake_base64 = base64.urlsafe_b64encode(fake_token)
        assert is_encrypted(fake_base64) is False

    def test_handles_very_long_fernet_tokens(self) -> None:
        """Test that is_encrypted works with very long Fernet tokens."""
        service = EncryptionService(secret=SecretStr("test-secret"))
        # Very long data
        long_data = b"x" * 100000
        ciphertext = service.encrypt(long_data)
        assert is_encrypted(ciphertext) is True
