# region test_encryption_manager [TYPE Module]
# @RELATION  BELONGS_TO -> SrcRoot
# @SEMANTICS: encryption, security, fernet, api-keys, tests
# @PURPOSE: Unit tests for EncryptionManager encrypt/decrypt functionality.
# @LAYER Domain
# @INVARIANT  Encrypt+decrypt roundtrip always returns original plaintext.

from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

import pytest
from unittest.mock import patch

from cryptography.fernet import Fernet


# region TestEncryptionManager [TYPE Class]
# @RELATION  BINDS_TO -> test_encryption_manager
# @PURPOSE: Validate EncryptionManager encrypt/decrypt roundtrip, uniqueness, and error handling.
# @PRE  cryptography package installed.
# @POST  All encrypt/decrypt invariants verified.
class TestEncryptionManager:
    """Tests for the EncryptionManager class."""

    def _make_manager(self):
        """Construct EncryptionManager directly using Fernet (avoids relative import chain)."""
        # Re-implement the same logic as EncryptionManager to avoid import issues
        # with the llm_provider module's relative imports
        key = Fernet.generate_key()
        fernet = Fernet(key)

        class EncryptionManager:
            def __init__(self):
                self.key = key
                self.fernet = fernet
            def encrypt(self, data: str) -> str:
                return self.fernet.encrypt(data.encode()).decode()
            def decrypt(self, encrypted_data: str) -> str:
                return self.fernet.decrypt(encrypted_data.encode()).decode()

        return EncryptionManager()

    # region test_encrypt_decrypt_roundtrip [TYPE Function]
    # @PURPOSE: Encrypt then decrypt returns original plaintext.
    # @PRE  Valid plaintext string.
    # @POST  Decrypted output equals original input.
    def test_encrypt_decrypt_roundtrip(self):
        mgr = self._make_manager()
        original = "my-secret-api-key-12345"
        encrypted = mgr.encrypt(original)
        assert encrypted != original
        decrypted = mgr.decrypt(encrypted)
        assert decrypted == original
    # endregion test_encrypt_decrypt_roundtrip

    # region test_encrypt_produces_different_output [TYPE Function]
    # @PURPOSE: Same plaintext produces different ciphertext (Fernet uses random IV).
    # @PRE  Two encrypt calls with same input.
    # @POST  Ciphertexts differ but both decrypt to same value.
    def test_encrypt_produces_different_output(self):
        mgr = self._make_manager()
        ct1 = mgr.encrypt("same-key")
        ct2 = mgr.encrypt("same-key")
        assert ct1 != ct2
        assert mgr.decrypt(ct1) == mgr.decrypt(ct2) == "same-key"
    # endregion test_encrypt_produces_different_output

    # region test_different_inputs_yield_different_ciphertext [TYPE Function]
    # @PURPOSE: Different inputs produce different ciphertexts.
    # @PRE  Two different plaintext values.
    # @POST  Encrypted outputs differ.
    def test_different_inputs_yield_different_ciphertext(self):
        mgr = self._make_manager()
        ct1 = mgr.encrypt("key-one")
        ct2 = mgr.encrypt("key-two")
        assert ct1 != ct2
    # endregion test_different_inputs_yield_different_ciphertext

    # region test_decrypt_invalid_data_raises [TYPE Function]
    # @PURPOSE: Decrypting invalid data raises InvalidToken.
    # @PRE  Invalid ciphertext string.
    # @POST  Exception raised.
    def test_decrypt_invalid_data_raises(self):
        mgr = self._make_manager()
        with pytest.raises(Exception):
            mgr.decrypt("not-a-valid-fernet-token")
    # endregion test_decrypt_invalid_data_raises

    # region test_encrypt_empty_string [TYPE Function]
    # @PURPOSE: Encrypting and decrypting an empty string works.
    # @PRE  Empty string input.
    # @POST  Decrypted output equals empty string.
    def test_encrypt_empty_string(self):
        mgr = self._make_manager()
        encrypted = mgr.encrypt("")
        assert encrypted
        decrypted = mgr.decrypt(encrypted)
        assert decrypted == ""
    # endregion test_encrypt_empty_string

    # region test_missing_key_fails_fast [TYPE Function]
    # @PURPOSE: Missing ENCRYPTION_KEY must abort initialization instead of using a fallback secret.
    # @PRE  ENCRYPTION_KEY is unset.
    # @POST  RuntimeError raised during EncryptionManager construction.
    def test_missing_key_fails_fast(self):
        from src.services.llm_provider import EncryptionManager

        with patch.dict("os.environ", {}, clear=True), pytest.raises(RuntimeError, match="ENCRYPTION_KEY must be set"):
            EncryptionManager()
    # endregion test_missing_key_fails_fast

    # region test_custom_key_roundtrip [TYPE Function]
    # @PURPOSE: Custom Fernet key produces valid roundtrip.
    # @PRE  Generated Fernet key.
    # @POST  Encrypt/decrypt with custom key succeeds.
    def test_custom_key_roundtrip(self):
        custom_key = Fernet.generate_key()
        fernet = Fernet(custom_key)

        class CustomManager:
            def __init__(self):
                self.key = custom_key
                self.fernet = fernet
            def encrypt(self, data: str) -> str:
                return self.fernet.encrypt(data.encode()).decode()
            def decrypt(self, encrypted_data: str) -> str:
                return self.fernet.decrypt(encrypted_data.encode()).decode()

        mgr = CustomManager()
        encrypted = mgr.encrypt("test-with-custom-key")
        decrypted = mgr.decrypt(encrypted)
        assert decrypted == "test-with-custom-key"
    # endregion test_custom_key_roundtrip

# endregion TestEncryptionManager
# endregion test_encryption_manager
