"""
Critical tests for crypto layer.

These tests verify the core cryptographic operations that are
essential for the security and correctness of Kilit.
"""

import pytest
from kilit.crypto import (
    Encryptor,
    Envelope,
    generate_master_key,
    validate_scrypt_params,
)
from cryptography.exceptions import InvalidTag


class TestEncryptionRoundtrip:
    """Test encrypt→decrypt roundtrip with various inputs."""

    def test_roundtrip_simple_text(self):
        """Encrypt and decrypt simple text."""
        master_key = b"0" * 32
        encryptor = Encryptor(master_key)

        plaintext = "Hello, World!"
        envelope = encryptor.encrypt(plaintext)
        decrypted = encryptor.decrypt(envelope)

        assert decrypted == plaintext

    def test_roundtrip_empty_string(self):
        """Encrypt and decrypt empty string."""
        master_key = b"0" * 32
        encryptor = Encryptor(master_key)

        plaintext = ""
        envelope = encryptor.encrypt(plaintext)
        decrypted = encryptor.decrypt(envelope)

        assert decrypted == plaintext

    def test_roundtrip_unicode(self):
        """Encrypt and decrypt unicode text."""
        master_key = b"0" * 32
        encryptor = Encryptor(master_key)

        plaintext = "Hello 世界 🔐 Türkçe"
        envelope = encryptor.encrypt(plaintext)
        decrypted = encryptor.decrypt(envelope)

        assert decrypted == plaintext

    def test_roundtrip_json_structure(self):
        """Encrypt and decrypt JSON-like structure."""
        master_key = b"0" * 32
        encryptor = Encryptor(master_key)

        plaintext = '{"database": {"password": "secret123"}}'
        envelope = encryptor.encrypt(plaintext)
        decrypted = encryptor.decrypt(envelope)

        assert decrypted == plaintext

    def test_roundtrip_multiline(self):
        """Encrypt and decrypt multiline text."""
        master_key = b"0" * 32
        encryptor = Encryptor(master_key)

        plaintext = "Line 1\nLine 2\nLine 3"
        envelope = encryptor.encrypt(plaintext)
        decrypted = encryptor.decrypt(envelope)

        assert decrypted == plaintext


class TestWrongMasterKey:
    """Test that wrong master key raises InvalidTag."""

    def test_wrong_key_raises_invalid_tag(self):
        """Decryption with wrong key raises InvalidTag."""
        master_key1 = b"1" * 32
        master_key2 = b"2" * 32

        encryptor1 = Encryptor(master_key1)
        encryptor2 = Encryptor(master_key2)

        plaintext = "secret data"
        envelope = encryptor1.encrypt(plaintext)

        with pytest.raises(InvalidTag):
            encryptor2.decrypt(envelope)

    def test_corrupted_ciphertext_raises_invalid_tag(self):
        """Corrupted ciphertext raises InvalidTag."""
        master_key = b"0" * 32
        encryptor = Encryptor(master_key)

        plaintext = "secret data"
        envelope = encryptor.encrypt(plaintext)

        # Corrupt the ciphertext
        envelope["ct"] = "corrupted_base64_data"

        with pytest.raises((InvalidTag, ValueError)):
            encryptor.decrypt(envelope)


class TestEnvelopeVersion:
    """Test envelope version validation."""

    def test_unsupported_version_raises_error(self):
        """Envelope with version != 1 raises ValueError."""
        envelope_dict = {
            "v": 2,  # Unsupported version
            "kdf": "scrypt",
            "kdfp": {"n": 16384, "r": 8, "p": 1},
            "salt": "dGVzdA",
            "nonce": "dGVzdA",
            "ct": "dGVzdA",
        }

        with pytest.raises(ValueError, match="Unsupported envelope version"):
            Envelope.from_dict(envelope_dict)

    def test_missing_version_raises_error(self):
        """Envelope without version raises KeyError."""
        envelope_dict = {
            "kdf": "scrypt",
            "kdfp": {"n": 16384, "r": 8, "p": 1},
            "salt": "dGVzdA",
            "nonce": "dGVzdA",
            "ct": "dGVzdA",
        }

        with pytest.raises(KeyError, match="v"):
            Envelope.from_dict(envelope_dict)


class TestEnvelopeKdfp:
    """Test envelope kdfp field validation."""

    def test_missing_kdfp_raises_error(self):
        """Envelope without kdfp raises KeyError."""
        envelope_dict = {
            "v": 1,
            "kdf": "scrypt",
            "salt": "dGVzdA",
            "nonce": "dGVzdA",
            "ct": "dGVzdA",
        }

        with pytest.raises(KeyError, match="kdfp"):
            Envelope.from_dict(envelope_dict)

    def test_invalid_kdfp_structure_raises_error(self):
        """Envelope with invalid kdfp structure raises ValueError."""
        envelope_dict = {
            "v": 1,
            "kdf": "scrypt",
            "kdfp": "not-a-dict",  # Should be dict
            "salt": "dGVzdA",
            "nonce": "dGVzdA",
            "ct": "dGVzdA",
        }

        with pytest.raises(ValueError, match="Invalid kdfp"):
            Envelope.from_dict(envelope_dict)

    def test_missing_kdfp_fields_raises_error(self):
        """Envelope with incomplete kdfp raises ValueError."""
        envelope_dict = {
            "v": 1,
            "kdf": "scrypt",
            "kdfp": {"n": 16384},  # Missing r and p
            "salt": "dGVzdA",
            "nonce": "dGVzdA",
            "ct": "dGVzdA",
        }

        with pytest.raises(ValueError, match="Missing kdfp fields"):
            Envelope.from_dict(envelope_dict)


class TestScryptParams:
    """Test Scrypt parameter validation."""

    def test_valid_params_pass(self):
        """Valid Scrypt parameters pass validation."""
        validate_scrypt_params(n=16384, r=8, p=1)
        validate_scrypt_params(n=32768, r=8, p=1)
        validate_scrypt_params(n=65536, r=16, p=2)

    def test_n_not_power_of_2_raises_error(self):
        """n not power of 2 raises ValueError."""
        with pytest.raises(ValueError, match="power of 2"):
            validate_scrypt_params(n=16383, r=8, p=1)

    def test_n_below_threshold_raises_error(self):
        """n below minimum threshold raises ValueError."""
        with pytest.raises(ValueError, match="must be >= 16384"):
            validate_scrypt_params(n=8192, r=8, p=1)

    def test_r_less_than_1_raises_error(self):
        """r < 1 raises ValueError."""
        with pytest.raises(ValueError, match="'r' must be >= 1"):
            validate_scrypt_params(n=16384, r=0, p=1)

    def test_p_less_than_1_raises_error(self):
        """p < 1 raises ValueError."""
        with pytest.raises(ValueError, match="'p' must be >= 1"):
            validate_scrypt_params(n=16384, r=8, p=0)


class TestEncryptionNonDeterminism:
    """Test that encryption is non-deterministic."""

    def test_same_plaintext_different_ciphertexts(self):
        """Same plaintext produces different ciphertexts."""
        master_key = b"0" * 32
        encryptor = Encryptor(master_key)

        plaintext = "same text"
        envelope1 = encryptor.encrypt(plaintext)
        envelope2 = encryptor.encrypt(plaintext)

        # Different salts and nonces
        assert envelope1["salt"] != envelope2["salt"]
        assert envelope1["nonce"] != envelope2["nonce"]
        assert envelope1["ct"] != envelope2["ct"]

        # But both decrypt to same plaintext
        assert encryptor.decrypt(envelope1) == plaintext
        assert encryptor.decrypt(envelope2) == plaintext


class TestKdfpInEnvelope:
    """Test that Scrypt parameters are stored in envelope."""

    def test_encrypt_stores_kdfp(self):
        """Encryption stores Scrypt parameters in envelope."""
        master_key = b"0" * 32
        encryptor = Encryptor(master_key, n=32768, r=16, p=2)

        plaintext = "test"
        envelope = encryptor.encrypt(plaintext)

        assert "kdfp" in envelope
        assert envelope["kdfp"]["n"] == 32768
        assert envelope["kdfp"]["r"] == 16
        assert envelope["kdfp"]["p"] == 2

    def test_decrypt_uses_envelope_kdfp(self):
        """Decryption uses Scrypt parameters from envelope."""
        master_key = b"0" * 32

        # Encrypt with custom params
        encryptor1 = Encryptor(master_key, n=32768, r=16, p=2)
        plaintext = "test"
        envelope = encryptor1.encrypt(plaintext)

        # Decrypt with different params (should use envelope params)
        encryptor2 = Encryptor(master_key, n=16384, r=8, p=1)
        decrypted = encryptor2.decrypt(envelope)

        assert decrypted == plaintext


class TestKeyGeneration:
    """Test master key generation."""

    def test_generates_32_bytes(self):
        """Generated key decodes to 32 bytes."""
        import base64

        key = generate_master_key()
        padding = "=" * (4 - len(key) % 4) if len(key) % 4 else ""
        decoded = base64.urlsafe_b64decode(key + padding)

        assert len(decoded) == 32

    def test_generates_different_keys(self):
        """Multiple generations produce different keys."""
        key1 = generate_master_key()
        key2 = generate_master_key()

        assert key1 != key2

    def test_no_padding(self):
        """Generated key has no padding."""
        key = generate_master_key()
        assert "=" not in key
