#!/usr/bin/env python3
"""
Integration Tests for Sudarshan Engine Box-in-a-Box Security Model

Tests the complete security stack from Inner Shield to Transaction Capsule.
"""

import pytest
import tempfile
import os
from pathlib import Path
import secrets
from unittest.mock import Mock

# Import test utilities
from .. import TestFixture, get_test_data, get_test_metadata, assert_security_properties

# Import components to test
from sudarshan.protocols import (
    InnerShield, OuterVault, IsolationRoom, TransactionCapsule
)
from sudarshan.spq_workflow import SPQWorkflow
from security.security_manager import SecurityManager, SecurityContext, SecurityLevel


class TestInnerShield(TestFixture):
    """Test Inner Shield protocol integration."""

    def setup_method(self):
        super().setup_method()
        self.inner_shield = InnerShield()
        self.security = SecurityManager()

    @assert_security_properties
    def test_legacy_asset_wrapping(self):
        """Test wrapping legacy cryptographic assets."""
        context = SecurityContext(
            user_id="test_user",
            operation="asset_wrapping",
            security_level=SecurityLevel.HIGH
        )

        # Simulate legacy private key (Bitcoin/Ethereum style)
        legacy_key = secrets.token_bytes(32)  # 256-bit key

        # Wrap the legacy asset
        wrapped_asset = self.inner_shield.wrap_legacy_asset(legacy_key, context)

        assert wrapped_asset is not None
        assert 'pqc_public_key' in wrapped_asset
        assert 'encrypted_legacy' in wrapped_asset
        assert 'wrapping_metadata' in wrapped_asset

        # Verify the wrapped asset can be unwrapped
        unwrapped_key = self.inner_shield.unwrap_legacy_asset(wrapped_asset, context)
        assert unwrapped_key == legacy_key

    @assert_security_properties
    def test_pqc_keypair_generation(self):
        """Test PQC keypair generation for asset protection."""
        context = SecurityContext(
            user_id="test_user",
            operation="keypair_generation",
            security_level=SecurityLevel.CRITICAL
        )

        # Generate PQC keypair for wrapping
        keypair = self.inner_shield.generate_wrapping_keypair("kyber1024", context)

        assert keypair is not None
        assert 'public_key' in keypair
        assert 'secret_key' in keypair
        assert len(keypair['public_key']) > 0
        assert len(keypair['secret_key']) > 0

    @assert_security_properties
    def test_asset_abstraction(self):
        """Test abstraction of original cryptographic material."""
        context = SecurityContext(
            user_id="test_user",
            operation="asset_abstraction",
            security_level=SecurityLevel.HIGH
        )

        # Original asset (could be any crypto)
        original_asset = {
            'type': 'bitcoin_private_key',
            'key_data': secrets.token_bytes(32),
            'metadata': {'network': 'mainnet', 'compressed': True}
        }

        # Abstract the asset
        abstracted = self.inner_shield.abstract_asset(original_asset, context)

        assert abstracted is not None
        assert 'asset_id' in abstracted
        assert 'pqc_wrapper' in abstracted
        assert 'abstraction_metadata' in abstracted

        # Verify abstraction preserves essential properties
        assert abstracted['abstraction_metadata']['original_type'] == 'bitcoin_private_key'

    def test_forbidden_direct_access(self):
        """Test that direct access to original keys is forbidden."""
        context = SecurityContext(
            user_id="test_user",
            operation="direct_access_test",
            security_level=SecurityLevel.BASIC
        )

        legacy_key = secrets.token_bytes(32)

        # This should raise an exception or return None
        with pytest.raises(SecurityError):
            self.inner_shield.get_raw_legacy_key(legacy_key, context)


class TestOuterVault(TestFixture):
    """Test Outer Vault protocol integration."""

    def setup_method(self):
        super().setup_method()
        self.outer_vault = OuterVault()
        self.security = SecurityManager()

    @assert_security_properties
    def test_multi_factor_authentication(self):
        """Test MFA with PQC signatures."""
        context = SecurityContext(
            user_id="test_user",
            operation="mfa_test",
            security_level=SecurityLevel.HIGH
        )

        # Simulate multiple authentication factors
        factors = [
            {
                'type': 'pqc_signature',
                'algorithm': 'dilithium5',
                'challenge': secrets.token_bytes(32),
                'signature': None  # Would be generated by authenticator
            },
            {
                'type': 'password',
                'hash': 'argon2_hash_of_password',
                'salt': secrets.token_bytes(16)
            },
            {
                'type': 'hardware_token',
                'token_id': 'yubi123',
                'counter': 42
            }
        ]

        # Authenticate with MFA
        auth_result = self.outer_vault.authenticate_mfa(factors, context)

        assert auth_result is not None
        assert auth_result['authenticated'] is True
        assert 'session_key' in auth_result
        assert 'factors_verified' in auth_result

    @assert_security_properties
    def test_vault_session_management(self):
        """Test vault session creation and management."""
        context = SecurityContext(
            user_id="test_user",
            operation="vault_session",
            security_level=SecurityLevel.HIGH
        )

        # Create vault session
        session = self.outer_vault.create_vault_session(context)

        assert session is not None
        assert 'session_id' in session
        assert 'encryption_key' in session
        assert 'valid_until' in session
        assert 'security_level' in session

        # Verify session is active
        assert self.outer_vault.is_session_active(session['session_id'])

        # Expire session
        self.outer_vault.expire_session(session['session_id'])
        assert not self.outer_vault.is_session_active(session['session_id'])

    @assert_security_properties
    def test_pqc_only_cryptography(self):
        """Test that only PQC algorithms are allowed."""
        context = SecurityContext(
            user_id="test_user",
            operation="pqc_only_test",
            security_level=SecurityLevel.CRITICAL
        )

        # These should work (PQC algorithms)
        assert self.outer_vault.is_pqc_algorithm("kyber1024")
        assert self.outer_vault.is_pqc_algorithm("dilithium5")
        assert self.outer_vault.is_pqc_algorithm("falcon1024")

        # These should be rejected (legacy algorithms)
        assert not self.outer_vault.is_pqc_algorithm("rsa2048")
        assert not self.outer_vault.is_pqc_algorithm("ecdsa")
        assert not self.outer_vault.is_pqc_algorithm("aes256")

        # Attempting to use legacy should raise error
        with pytest.raises(SecurityError):
            self.outer_vault.validate_algorithm("rsa2048", context)


class TestIsolationRoom(TestFixture):
    """Test Isolation Room protocol integration."""

    def setup_method(self):
        super().setup_method()
        self.isolation_room = IsolationRoom()
        self.security = SecurityManager()

    @assert_security_properties
    def test_enclave_execution(self):
        """Test execution of operations within secure enclave."""
        context = SecurityContext(
            user_id="test_user",
            operation="enclave_test",
            security_level=SecurityLevel.CRITICAL
        )

        # Test data for enclave operation
        test_data = get_test_data(256)

        # Execute operation in enclave
        result = self.isolation_room.execute_in_enclave(
            operation="hash_data",
            data=test_data,
            context=context
        )

        assert result is not None
        assert 'enclave_result' in result
        assert 'attestation' in result
        assert 'execution_time' in result

    def test_hardware_detection(self):
        """Test detection of hardware security modules."""
        # Test HSM detection
        hsm_available = self.isolation_room.detect_hsm()
        assert isinstance(hsm_available, bool)

        # Test TPM detection
        tpm_available = self.isolation_room.detect_tpm()
        assert isinstance(tpm_available, bool)

        # Test SGX detection
        sgx_available = self.isolation_room.detect_sgx()
        assert isinstance(sgx_available, bool)

    @assert_security_properties
    def test_remote_attack_prevention(self):
        """Test prevention of remote side-channel attacks."""
        context = SecurityContext(
            user_id="test_user",
            operation="side_channel_test",
            security_level=SecurityLevel.CRITICAL
        )

        # Simulate potential side-channel attack
        attack_data = {
            'timing_attack': True,
            'power_analysis': True,
            'electromagnetic': True
        }

        # This should be blocked or mitigated
        result = self.isolation_room.mitigate_side_channel_attack(attack_data, context)

        assert result is not None
        assert result['attack_mitigated'] is True
        assert 'mitigation_techniques' in result

    @assert_security_properties
    def test_attestation_verification(self):
        """Test hardware attestation verification."""
        context = SecurityContext(
            user_id="test_user",
            operation="attestation_test",
            security_level=SecurityLevel.CRITICAL
        )

        # Generate mock attestation
        attestation = self.isolation_room.generate_mock_attestation()

        # Verify attestation
        is_valid = self.isolation_room.verify_attestation(attestation, context)

        assert isinstance(is_valid, bool)
        # In real implementation, this would verify against TPM/HSM


class TestTransactionCapsule(TestFixture):
    """Test Transaction Capsule protocol integration."""

    def setup_method(self):
        super().setup_method()
        self.transaction_capsule = TransactionCapsule()
        self.security = SecurityManager()

    @assert_security_properties
    def test_one_time_transaction(self):
        """Test one-time transaction capsule creation."""
        context = SecurityContext(
            user_id="test_user",
            operation="transaction_capsule",
            security_level=SecurityLevel.HIGH
        )

        # Transaction data
        transaction_data = {
            'type': 'bitcoin_transaction',
            'amount': 0.001,
            'recipient': '1A2B3C...',
            'fee': 0.00001
        }

        # Create transaction capsule
        capsule = self.transaction_capsule.create_transaction_capsule(
            transaction_data, context
        )

        assert capsule is not None
        assert 'capsule_id' in capsule
        assert 'encrypted_transaction' in capsule
        assert 'pqc_signature' in capsule
        assert 'one_time_key' in capsule
        assert 'validity_period' in capsule

    @assert_security_properties
    def test_key_non_reuse_guarantee(self):
        """Test that transaction keys are never reused."""
        context = SecurityContext(
            user_id="test_user",
            operation="key_reuse_test",
            security_level=SecurityLevel.CRITICAL
        )

        # Create multiple transaction capsules
        capsules = []
        for i in range(10):
            transaction_data = {
                'type': 'test_transaction',
                'sequence': i,
                'data': secrets.token_bytes(32)
            }

            capsule = self.transaction_capsule.create_transaction_capsule(
                transaction_data, context
            )
            capsules.append(capsule)

        # Extract all one-time keys
        keys = [capsule['one_time_key'] for capsule in capsules]

        # All keys should be unique
        assert len(set(keys)) == len(keys)

        # Verify no key reuse
        for i, key1 in enumerate(keys):
            for j, key2 in enumerate(keys):
                if i != j:
                    assert key1 != key2, f"Key reuse detected between capsules {i} and {j}"

    @assert_security_properties
    def test_stateless_transaction_processing(self):
        """Test stateless transaction processing."""
        context = SecurityContext(
            user_id="test_user",
            operation="stateless_test",
            security_level=SecurityLevel.HIGH
        )

        # Create transaction capsule
        transaction_data = {
            'type': 'payment',
            'amount': 100.0,
            'currency': 'USD'
        }

        capsule = self.transaction_capsule.create_transaction_capsule(
            transaction_data, context
        )

        # Process transaction statelessly
        result = self.transaction_capsule.process_transaction_stateless(
            capsule, context
        )

        assert result is not None
        assert result['processed'] is True
        assert 'transaction_id' in result
        assert 'processing_time' in result

        # Verify capsule is destroyed after processing
        assert self.transaction_capsule.is_capsule_destroyed(capsule['capsule_id'])

    def test_replay_attack_prevention(self):
        """Test prevention of replay attacks."""
        context = SecurityContext(
            user_id="test_user",
            operation="replay_test",
            security_level=SecurityLevel.HIGH
        )

        # Create transaction capsule
        transaction_data = {'type': 'transfer', 'amount': 50.0}
        capsule = self.transaction_capsule.create_transaction_capsule(
            transaction_data, context
        )

        # Process transaction first time
        result1 = self.transaction_capsule.process_transaction_stateless(
            capsule, context
        )
        assert result1['processed'] is True

        # Attempt to replay (should fail)
        result2 = self.transaction_capsule.process_transaction_stateless(
            capsule, context
        )
        assert result2['processed'] is False
        assert 'error' in result2
        assert 'replay_detected' in result2['error']


class TestBoxInBoxIntegration(TestFixture):
    """Test complete Box-in-a-Box integration."""

    def setup_method(self):
        super().setup_method()
        self.inner_shield = InnerShield()
        self.outer_vault = OuterVault()
        self.isolation_room = IsolationRoom()
        self.transaction_capsule = TransactionCapsule()
        self.spq_workflow = SPQWorkflow()
        self.security = SecurityManager()

    @assert_security_properties
    def test_complete_security_workflow(self):
        """Test complete workflow from asset to transaction."""
        context = SecurityContext(
            user_id="test_user",
            operation="complete_workflow",
            security_level=SecurityLevel.CRITICAL
        )

        # Step 1: Wrap legacy asset (Inner Shield)
        legacy_asset = secrets.token_bytes(32)
        wrapped_asset = self.inner_shield.wrap_legacy_asset(legacy_asset, context)

        # Step 2: Create vault session (Outer Vault)
        vault_session = self.outer_vault.create_vault_session(context)

        # Step 3: Execute in enclave (Isolation Room)
        enclave_result = self.isolation_room.execute_in_enclave(
            operation="validate_asset",
            data=wrapped_asset,
            context=context
        )

        # Step 4: Create transaction capsule
        transaction_data = {
            'asset_id': wrapped_asset['asset_id'],
            'action': 'transfer',
            'amount': 1.0
        }

        capsule = self.transaction_capsule.create_transaction_capsule(
            transaction_data, context
        )

        # Step 5: Create .spq file with all security layers
        metadata = get_test_metadata()
        metadata.update({
            'security_layers': ['inner_shield', 'outer_vault', 'isolation_room', 'transaction_capsule'],
            'workflow_complete': True
        })

        with tempfile.NamedTemporaryFile(suffix='.spq', delete=False) as tmp_file:
            spq_file_path = tmp_file.name

        try:
            # Create .spq file
            spq_result = self.spq_workflow.create_spq_file(
                file_path=spq_file_path,
                payload=capsule,
                metadata=metadata,
                context=context
            )

            assert spq_result['success'] is True
            assert os.path.exists(spq_file_path)

            # Read and verify .spq file
            read_result = self.spq_workflow.read_spq_file(
                file_path=spq_file_path,
                context=context
            )

            assert read_result['success'] is True
            assert read_result['payload']['asset_id'] == wrapped_asset['asset_id']

        finally:
            # Cleanup
            if os.path.exists(spq_file_path):
                os.unlink(spq_file_path)

    @assert_security_properties
    def test_security_layer_isolation(self):
        """Test that each security layer operates independently."""
        context = SecurityContext(
            user_id="test_user",
            operation="layer_isolation_test",
            security_level=SecurityLevel.CRITICAL
        )

        # Test data
        test_asset = secrets.token_bytes(32)

        # Layer 1: Inner Shield
        wrapped = self.inner_shield.wrap_legacy_asset(test_asset, context)
        assert wrapped is not None

        # Layer 2: Outer Vault (independent of Layer 1)
        session = self.outer_vault.create_vault_session(context)
        assert session is not None
        assert session['session_id'] != wrapped.get('asset_id')

        # Layer 3: Isolation Room (independent of others)
        enclave_result = self.isolation_room.execute_in_enclave(
            operation="test_operation",
            data=b"test",
            context=context
        )
        assert enclave_result is not None

        # Layer 4: Transaction Capsule (independent of others)
        capsule = self.transaction_capsule.create_transaction_capsule(
            {'test': 'data'}, context
        )
        assert capsule is not None

        # Verify all layers can coexist
        assert len({wrapped['asset_id'], session['session_id'], capsule['capsule_id']}) == 3

    def test_failure_propagation(self):
        """Test that failures propagate correctly through layers."""
        context = SecurityContext(
            user_id="test_user",
            operation="failure_test",
            security_level=SecurityLevel.BASIC
        )

        # Simulate failure in inner shield
        with pytest.raises(SecurityError):
            self.inner_shield.wrap_legacy_asset(None, context)

        # Simulate failure in outer vault
        with pytest.raises(SecurityError):
            self.outer_vault.authenticate_mfa([], context)

        # Simulate failure in isolation room
        with pytest.raises(SecurityError):
            self.isolation_room.execute_in_enclave(
                operation="invalid_operation",
                data=b"",
                context=context
            )

        # Simulate failure in transaction capsule
        with pytest.raises(SecurityError):
            self.transaction_capsule.create_transaction_capsule(None, context)


if __name__ == "__main__":
    pytest.main([__file__, "-v"])