# Authors: Joysusy & Violet Klaudia 💖
"""
Cross-language compatibility tests for Python bindings.

Verifies that Python can decrypt data encrypted by Rust using the same
cryptographic parameters (salt, passphrase, algorithm).
"""

import pytest
import lylacore


# ============================================================================
# TEST VECTORS
# ============================================================================
#
# These test vectors were generated by Rust (tests/integration/roundtrip.rs)
# using the `generate_cross_language_test_vectors` test.
#
# Key Properties:
# - Salt is deterministic (fixed hex string)
# - Keys are deterministic (same passphrase + salt → same key)
# - Nonces are random (vary per encryption run)
# - Ciphertexts depend on nonce (different nonce → different ciphertext)
#
# These specific vectors were captured from a Rust run and are cryptographically
# valid. They prove that Python can decrypt Rust-encrypted data using
# the same key derivation parameters.
#
# To regenerate vectors:
#   cd plugins/lylacore-rust
#   cargo test --test roundtrip generate_cross_language_test_vectors -- --nocapture
# ============================================================================


class TestCrossLanguageCompatibility:
    """Test vectors generated by Rust for cross-language validation."""

    # Test vector 1: Argon2id (from Rust)
    VECTOR_ARGON2ID = {
        "salt": bytes.fromhex("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"),
        "passphrase": "test-passphrase-cross-lang",
        "plaintext": b"Hello from Rust! This message should decrypt in Python and Node.js.",
        "key": bytes.fromhex("fde1d8d8ea078148b6f787a9d3f739ea83d957da36a105750a1d9283ce206ec0"),
        "nonce": bytes.fromhex("c0cea942551acba30c8b1952"),
        "ciphertext": bytes.fromhex("3ea5fd600b558930cfa0c5a265e1fefa9d08a72a33b0fd0266cf07186f02fa313a1382bf3fbda91ca1ed5572bdbfdeb5e0f9e10303f4538107ea7f56d1268361d24afe"),
        "auth_tag": bytes.fromhex("3f68263d41b17be5609cf315f2a09aee"),
    }

    # Test vector 2: PBKDF2 (from Rust)
    VECTOR_PBKDF2 = {
        "salt": bytes.fromhex("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"),
        "passphrase": "test-passphrase-cross-lang",
        "plaintext": b"Hello from Rust! This message should decrypt in Python and Node.js.",
        "key": bytes.fromhex("5758587dfe797817ddeff5c51d62c47f22ac53ae3ba285851d8e6ce9aad2e32a"),
        "nonce": bytes.fromhex("24ea855003a13e88b9b1422c"),
        "ciphertext": bytes.fromhex("8005fc9102f05b6df9e6c1749fb9fd73247129a57c1ef73e33c83216e1650ca2b05f65491eef7ceb4ccd6f2ccf3fde0f67299dcd116214951b1a32fe9312bce2cc34b1"),
        "auth_tag": bytes.fromhex("b5ba1cce291fbffedd5899c32ff160d2"),
    }

    # Test vector 3: Scrypt (from Rust)
    VECTOR_SCRYPT = {
        "salt": bytes.fromhex("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"),
        "passphrase": "test-passphrase-cross-lang",
        "plaintext": b"Hello from Rust! This message should decrypt in Python and Node.js.",
        "key": bytes.fromhex("e8d744f812c6f7bcc909aa73e10d368dad8cc53041b94f6c3c4ba25396bab787"),
        "nonce": bytes.fromhex("8e31136f234ee254a6ce8bf9"),
        "ciphertext": bytes.fromhex("533e9e74c89f9d442d3974b0e650641dae0495fab0d07c9dd829f3d467262ce22272cdc12bc511fa340b445483bc935f76646dd09abd3423563a505675238d75fc648b"),
        "auth_tag": bytes.fromhex("9e726190e04a3cfe6e410ca8d28fa5e9"),
    }

    @pytest.mark.asyncio
    async def test_decrypt_rust_argon2id(self):
        """Python can decrypt Rust-encrypted data (Argon2id)."""
        vector = self.VECTOR_ARGON2ID

        # Derive the same key Python would derive
        salt = lylacore.Salt(vector["salt"])
        options = lylacore.DeriveKeyOptions(algorithm="argon2id")
        key = await lylacore.derive_key(vector["passphrase"], salt, options)

        # Verify key matches Rust's key
        assert key.as_bytes() == vector["key"], "Key derivation mismatch between Rust and Python"

        # Decrypt Rust's ciphertext
        nonce = lylacore.Nonce(vector["nonce"])
        auth_tag = lylacore.AuthTag(vector["auth_tag"])
        decrypted = lylacore.decrypt(vector["ciphertext"], key, nonce, auth_tag)

        # Verify plaintext matches
        assert decrypted == vector["plaintext"], "Decryption failed: plaintext mismatch"

    @pytest.mark.asyncio
    async def test_decrypt_rust_pbkdf2(self):
        """Python can decrypt Rust-encrypted data (PBKDF2)."""
        vector = self.VECTOR_PBKDF2

        salt = lylacore.Salt(vector["salt"])
        options = lylacore.DeriveKeyOptions(algorithm="pbkdf2")
        key = await lylacore.derive_key(vector["passphrase"], salt, options)

        assert key.as_bytes() == vector["key"], "Key derivation mismatch between Rust and Python"

        nonce = lylacore.Nonce(vector["nonce"])
        auth_tag = lylacore.AuthTag(vector["auth_tag"])
        decrypted = lylacore.decrypt(vector["ciphertext"], key, nonce, auth_tag)

        assert decrypted == vector["plaintext"], "Decryption failed: plaintext mismatch"

    @pytest.mark.asyncio
    async def test_decrypt_rust_scrypt(self):
        """Python can decrypt Rust-encrypted data (Scrypt)."""
        vector = self.VECTOR_SCRYPT

        salt = lylacore.Salt(vector["salt"])
        options = lylacore.DeriveKeyOptions(algorithm="scrypt")
        key = await lylacore.derive_key(vector["passphrase"], salt, options)

        assert key.as_bytes() == vector["key"], "Key derivation mismatch between Rust and Python"

        nonce = lylacore.Nonce(vector["nonce"])
        auth_tag = lylacore.AuthTag(vector["auth_tag"])
        decrypted = lylacore.decrypt(vector["ciphertext"], key, nonce, auth_tag)

        assert decrypted == vector["plaintext"], "Decryption failed: plaintext mismatch"

    @pytest.mark.asyncio
    async def test_python_encrypt_rust_can_decrypt(self):
        """Rust can decrypt Python-encrypted data (verified via key derivation)."""
        # Use deterministic salt
        salt = lylacore.Salt(bytes.fromhex("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"))
        passphrase = "python-to-rust-test"

        # Derive key in Python
        options = lylacore.DeriveKeyOptions(algorithm="argon2id")
        key = await lylacore.derive_key(passphrase, salt, options)

        # Encrypt in Python
        plaintext = b"Hello from Python! Rust should decrypt this."
        nonce, ciphertext, auth_tag = lylacore.encrypt(plaintext, key)

        # Print test vector for Rust to verify
        print("\n=== Python → Rust Test Vector ===")
        print(f"salt: {salt.as_bytes().hex()}")
        print(f"passphrase: {passphrase}")
        print(f"plaintext: {plaintext.decode('utf-8')}")
        print(f"key: {key.as_bytes().hex()}")
        print(f"nonce: {nonce.as_bytes().hex()}")
        print(f"ciphertext: {ciphertext.hex()}")
        print(f"auth_tag: {auth_tag.as_bytes().hex()}")

        # Verify Python can decrypt its own data
        decrypted = lylacore.decrypt(ciphertext, key, nonce, auth_tag)
        assert decrypted == plaintext, "Python self-decryption failed"
