"""Tests for encryption providers."""

from __future__ import annotations

import pytest
from cryptography.fernet import Fernet

from vault.encryption import (
    AESGCMEncryption,
    EncryptionProvider,
    FernetEncryption,
    derive_key,
)


class TestDeriveKey:
    def test_produces_correct_length(self):
        key, salt = derive_key("my-password", length=32)
        assert len(key) == 32

    def test_deterministic_with_same_salt(self):
        key1, salt = derive_key("password", salt=b"fixed-salt")
        key2, _ = derive_key("password", salt=b"fixed-salt")
        assert key1 == key2

    def test_different_passwords_different_keys(self):
        key1, _ = derive_key("password-a", salt=b"same-salt")
        key2, _ = derive_key("password-b", salt=b"same-salt")
        assert key1 != key2

    def test_generates_random_salt_when_none(self):
        _, salt1 = derive_key("password")
        _, salt2 = derive_key("password")
        assert salt1 != salt2


class TestFernetEncryption:
    def test_roundtrip(self, fernet):
        plaintext = b"hello world"
        ct = fernet.encrypt(plaintext)
        assert fernet.decrypt(ct) == plaintext

    def test_ciphertext_differs_each_time(self, fernet):
        plaintext = b"same data"
        ct1 = fernet.encrypt(plaintext)
        ct2 = fernet.encrypt(plaintext)
        assert ct1 != ct2  # Fernet uses random IV

    def test_from_explicit_key(self):
        key = Fernet.generate_key()
        enc = FernetEncryption(key=key)
        ct = enc.encrypt(b"data")
        assert enc.decrypt(ct) == b"data"

    def test_requires_key_or_password(self):
        with pytest.raises(ValueError, match="Provide either"):
            FernetEncryption()

    def test_satisfies_protocol(self, fernet):
        assert isinstance(fernet, EncryptionProvider)

    def test_empty_plaintext(self, fernet):
        ct = fernet.encrypt(b"")
        assert fernet.decrypt(ct) == b""

    def test_large_payload(self, fernet):
        data = b"x" * 1_000_000
        ct = fernet.encrypt(data)
        assert fernet.decrypt(ct) == data


class TestAESGCMEncryption:
    def test_roundtrip(self, aesgcm):
        plaintext = b"hello aesgcm"
        ct = aesgcm.encrypt(plaintext)
        assert aesgcm.decrypt(ct) == plaintext

    def test_ciphertext_differs_each_time(self, aesgcm):
        plaintext = b"same data"
        ct1 = aesgcm.encrypt(plaintext)
        ct2 = aesgcm.encrypt(plaintext)
        assert ct1 != ct2  # random nonce

    def test_from_explicit_key(self):
        import os

        key = os.urandom(32)
        enc = AESGCMEncryption(key=key)
        ct = enc.encrypt(b"secret")
        assert enc.decrypt(ct) == b"secret"

    def test_rejects_wrong_key_length(self):
        with pytest.raises(ValueError, match="32-byte key"):
            AESGCMEncryption(key=b"too-short")

    def test_requires_key_or_password(self):
        with pytest.raises(ValueError, match="Provide either"):
            AESGCMEncryption()

    def test_satisfies_protocol(self, aesgcm):
        assert isinstance(aesgcm, EncryptionProvider)

    def test_tampered_ciphertext_fails(self, aesgcm):
        ct = aesgcm.encrypt(b"data")
        tampered = ct[:-1] + bytes([ct[-1] ^ 0xFF])
        with pytest.raises(Exception):
            aesgcm.decrypt(tampered)

    def test_empty_plaintext(self, aesgcm):
        ct = aesgcm.encrypt(b"")
        assert aesgcm.decrypt(ct) == b""
