"""Unit tests for encrypted storage functionality."""

import json
import tempfile
from pathlib import Path
from unittest.mock import patch, mock_open
import pytest

from src.security.encrypted_storage import (
    EncryptedStorage,
    encrypt_data,
    decrypt_data,
    generate_encryption_key,
)


class TestEncryptedStorage:
    """Test cases for EncryptedStorage class."""

    def setup_method(self):
        """Set up test fixtures."""
        self.temp_dir = tempfile.mkdtemp()
        self.storage_path = Path(self.temp_dir) / "encrypted_data"
        self.key_path = Path(self.temp_dir) / "key.bin"

    def teardown_method(self):
        """Clean up test fixtures."""
        import shutil
        if self.temp_dir and Path(self.temp_dir).exists():
            shutil.rmtree(self.temp_dir)

    def test_init_creates_storage_directory(self):
        """Test that EncryptedStorage creates storage directory if it doesn't exist."""
        storage_dir = Path(self.temp_dir) / "new_storage"
        storage_path = storage_dir / "data"
        
        # Directory shouldn't exist initially
        assert not storage_dir.exists()
        
        # Create EncryptedStorage instance
        storage = EncryptedStorage(storage_path, self.key_path)
        
        # Directory should be created
        assert storage_dir.exists()
        assert storage_dir.is_dir()

    def test_init_generates_new_key_if_not_exists(self):
        """Test that EncryptedStorage generates a new encryption key if it doesn't exist."""
        # Key file shouldn't exist initially
        assert not self.key_path.exists()
        
        # Create EncryptedStorage instance
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Key file should be created
        assert self.key_path.exists()
        assert self.key_path.is_file()

    def test_store_and_retrieve_string_data(self):
        """Test storing and retrieving string data."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Store string data
        test_data = "sensitive string data"
        key = "test_key"
        
        success = storage.store(key, test_data)
        assert success is True
        
        # Retrieve data
        retrieved_data = storage.retrieve(key)
        assert retrieved_data == test_data

    def test_store_and_retrieve_dict_data(self):
        """Test storing and retrieving dictionary data."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Store dictionary data
        test_data = {"user_id": 123, "api_key": "secret_key", "settings": {"theme": "dark"}}
        key = "user_config"
        
        success = storage.store(key, test_data)
        assert success is True
        
        # Retrieve data
        retrieved_data = storage.retrieve(key)
        assert retrieved_data == test_data

    def test_store_and_retrieve_list_data(self):
        """Test storing and retrieving list data."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Store list data
        test_data = ["item1", "item2", {"nested": "data"}, 123]
        key = "list_data"
        
        success = storage.store(key, test_data)
        assert success is True
        
        # Retrieve data
        retrieved_data = storage.retrieve(key)
        assert retrieved_data == test_data

    def test_store_and_retrieve_binary_data(self):
        """Test storing and retrieving binary data."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Store binary data
        test_data = b"binary sensitive data"
        key = "binary_key"
        
        success = storage.store(key, test_data)
        assert success is True
        
        # Retrieve data
        retrieved_data = storage.retrieve(key)
        assert retrieved_data == test_data

    def test_retrieve_nonexistent_key(self):
        """Test retrieving data for a key that doesn't exist."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Try to retrieve non-existent key
        result = storage.retrieve("nonexistent_key")
        assert result is None

    def test_overwrite_existing_key(self):
        """Test overwriting data for an existing key."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Store initial data
        key = "test_key"
        initial_data = "initial data"
        storage.store(key, initial_data)
        
        # Overwrite with new data
        new_data = "new data"
        success = storage.store(key, new_data)
        assert success is True
        
        # Retrieve should return new data
        retrieved_data = storage.retrieve(key)
        assert retrieved_data == new_data

    def test_delete_existing_key(self):
        """Test deleting data for an existing key."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Store data
        key = "test_key"
        test_data = "test data"
        storage.store(key, test_data)
        
        # Verify data exists
        assert storage.retrieve(key) == test_data
        
        # Delete data
        success = storage.delete(key)
        assert success is True
        
        # Verify data is gone
        assert storage.retrieve(key) is None

    def test_delete_nonexistent_key(self):
        """Test deleting data for a key that doesn't exist."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Try to delete non-existent key
        success = storage.delete("nonexistent_key")
        assert success is False

    def test_list_keys(self):
        """Test listing all stored keys."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Store multiple items
        storage.store("key1", "data1")
        storage.store("key2", "data2")
        storage.store("key3", "data3")
        
        # List keys
        keys = storage.list_keys()
        assert "key1" in keys
        assert "key2" in keys
        assert "key3" in keys
        assert len(keys) == 3

    def test_clear_all_data(self):
        """Test clearing all stored data."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Store multiple items
        storage.store("key1", "data1")
        storage.store("key2", "data2")
        
        # Verify data exists
        assert len(storage.list_keys()) == 2
        
        # Clear all data
        success = storage.clear()
        assert success is True
        
        # Verify all data is gone
        assert len(storage.list_keys()) == 0
        assert storage.retrieve("key1") is None
        assert storage.retrieve("key2") is None

    def test_handle_encryption_errors(self):
        """Test handling encryption errors gracefully."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Mock encryption to fail
        with patch.object(storage.fernet, 'encrypt', side_effect=Exception("Encryption failed")):
            success = storage.store("test_key", "test_data")
            assert success is False

    def test_handle_decryption_errors(self):
        """Test handling decryption errors gracefully."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Store valid data first
        storage.store("test_key", "test_data")
        
        # Corrupt the stored data
        storage_file = self.storage_path / "test_key.enc"
        with open(storage_file, "wb") as f:
            f.write(b"corrupted_data")
        
        # Try to retrieve corrupted data
        result = storage.retrieve("test_key")
        assert result is None

    def test_file_permissions(self):
        """Test that files have correct permissions."""
        storage = EncryptedStorage(self.storage_path, self.key_path)
        
        # Store data
        storage.store("test_key", "test_data")
        
        # Check key file permissions
        key_stat = self.key_path.stat()
        assert oct(key_stat.st_mode)[-3:] == "600"
        
        # Check storage file permissions
        storage_file = self.storage_path / "test_key.enc"
        storage_stat = storage_file.stat()
        assert oct(storage_stat.st_mode)[-3:] == "600"


class TestEncryptionUtilities:
    """Test cases for encryption utility functions."""

    def test_generate_encryption_key(self):
        """Test generating encryption key."""
        key = generate_encryption_key()
        assert len(key) == 44  # Fernet key length
        assert isinstance(key, bytes)

    def test_encrypt_and_decrypt_data(self):
        """Test encrypt and decrypt utility functions."""
        key = generate_encryption_key()
        test_data = "sensitive data"
        
        # Encrypt data
        encrypted = encrypt_data(test_data, key)
        assert isinstance(encrypted, bytes)
        assert encrypted != test_data.encode()
        
        # Decrypt data
        decrypted = decrypt_data(encrypted, key)
        assert decrypted == test_data

    def test_encrypt_and_decrypt_dict_data(self):
        """Test encrypt and decrypt dictionary data."""
        key = generate_encryption_key()
        test_data = {"user": "john", "password": "secret123"}
        
        # Encrypt data
        encrypted = encrypt_data(test_data, key)
        assert isinstance(encrypted, bytes)
        
        # Decrypt data
        decrypted = decrypt_data(encrypted, key)
        assert decrypted == test_data

    def test_encrypt_and_decrypt_binary_data(self):
        """Test encrypt and decrypt binary data."""
        key = generate_encryption_key()
        test_data = b"binary sensitive data"
        
        # Encrypt data
        encrypted = encrypt_data(test_data, key)
        assert isinstance(encrypted, bytes)
        assert encrypted != test_data
        
        # Decrypt data
        decrypted = decrypt_data(encrypted, key)
        assert decrypted == test_data

    def test_encrypt_with_invalid_key(self):
        """Test encrypt with invalid key."""
        invalid_key = b"invalid_key"
        test_data = "test data"
        
        with pytest.raises(Exception):
            encrypt_data(test_data, invalid_key)

    def test_decrypt_with_invalid_key(self):
        """Test decrypt with invalid key."""
        key = generate_encryption_key()
        test_data = "test data"
        encrypted = encrypt_data(test_data, key)
        
        invalid_key = b"invalid_key"
        with pytest.raises(Exception):
            decrypt_data(encrypted, invalid_key)
