<?php

declare(strict_types=1);

/**
 * State Encryption Security Integration Tests
 *
 * Tests the complete security stack integration:
 * 1. CSRF Protection
 * 2. Rate Limiting
 * 3. Idempotency
 * 4. State Encryption (NEW)
 * 5. Authorization
 *
 * These tests ensure that all security layers work together correctly
 * with encrypted state, especially for sensitive data scenarios.
 */

use App\Framework\Cache\Driver\InMemoryCache;
use App\Framework\Cache\SmartCache;
use App\Framework\Cryptography\CryptographicUtilities;
use App\Framework\LiveComponents\Serialization\EncryptedStateSerializer;
use App\Framework\LiveComponents\Serialization\StateEncryptor;
use App\Framework\Random\SecureRandomGenerator;
use App\Framework\StateManagement\CacheBasedStateManager;
use App\Framework\StateManagement\Transformers\EncryptionTransformer;
use App\Framework\StateManagement\SerializableState;

// Test State with Sensitive Data
final readonly class SensitiveUserState implements SerializableState
{
    public function __construct(
        public string $userId,
        public string $email,
        public string $sessionToken,  // Sensitive - should be encrypted
        public array $privateData     // Sensitive - should be encrypted
    ) {}

    public function toArray(): array
    {
        return [
            'user_id' => $this->userId,
            'email' => $this->email,
            'session_token' => $this->sessionToken,
            'private_data' => $this->privateData,
        ];
    }

    public static function fromArray(array $data): self
    {
        return new self(
            userId: $data['user_id'],
            email: $data['email'],
            sessionToken: $data['session_token'],
            privateData: $data['private_data']
        );
    }
}

describe('State Encryption Integration with Security Layers', function () {
    beforeEach(function () {
        // Setup encryption infrastructure
        $this->random = new SecureRandomGenerator();
        $this->crypto = new CryptographicUtilities($this->random);
        $this->encryptionKey = $this->random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

        $this->encryptor = new StateEncryptor(
            $this->encryptionKey,
            $this->crypto,
            $this->random
        );

        $this->serializer = new EncryptedStateSerializer($this->encryptor);

        // Setup in-memory cache for testing (wrapped in SmartCache)
        $cacheDriver = new InMemoryCache();
        $this->cache = new SmartCache($cacheDriver);

        // Test sensitive state
        $this->sensitiveState = new SensitiveUserState(
            userId: 'user-123',
            email: 'user@example.com',
            sessionToken: 'sensitive-token-abc123',
            privateData: [
                'ssn' => '123-45-6789',
                'credit_card' => '4111-1111-1111-1111',
                'api_key' => 'sk_live_secret123'
            ]
        );

        // Setup StateManager with encryption for SensitiveUserState
        $encryptionTransformer = new EncryptionTransformer(
            $this->serializer,
            SensitiveUserState::class
        );

        $this->stateManager = (new CacheBasedStateManager(
            cache: $this->cache,
            keyPrefix: 'livecomponent_state',
            stateClass: SensitiveUserState::class
        ))->addTransformer($encryptionTransformer);
    });

    describe('Encrypted State Storage', function () {
        it('stores state encrypted in cache', function () {
            $componentId = 'secure-component:test-1';

            // Store sensitive state
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Verify state is stored in cache
            $cacheKey = "livecomponent_state:{$componentId}";
            $cachedData = $this->cache->get($cacheKey);

            expect($cachedData)->not->toBeNull();

            // Cached data should be encrypted (base64 string)
            expect($cachedData)->toBeString();
            expect(base64_decode($cachedData, strict: true))->not->toBeFalse();

            // Cached data should NOT contain plaintext sensitive info
            expect($cachedData)->not->toContain('sensitive-token-abc123');
            expect($cachedData)->not->toContain('123-45-6789');
            expect($cachedData)->not->toContain('4111-1111-1111-1111');
            expect($cachedData)->not->toContain('sk_live_secret123');
        });

        it('retrieves and decrypts state correctly', function () {
            $componentId = 'secure-component:test-2';

            // Store and retrieve
            $this->stateManager->store($componentId, $this->sensitiveState);
            $retrieved = $this->stateManager->retrieve($componentId, SensitiveUserState::class);

            // Should decrypt to original state
            expect($retrieved)->toBeInstanceOf(SensitiveUserState::class);
            expect($retrieved->userId)->toBe('user-123');
            expect($retrieved->email)->toBe('user@example.com');
            expect($retrieved->sessionToken)->toBe('sensitive-token-abc123');
            expect($retrieved->privateData['ssn'])->toBe('123-45-6789');
        });

        it('prevents state tampering with MAC verification', function () {
            $componentId = 'secure-component:test-3';

            // Store encrypted state
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Get encrypted data from cache
            $cacheKey = "livecomponent_state:{$componentId}";
            $encryptedData = $this->cache->get($cacheKey);

            // Tamper with encrypted data (modify last 5 characters)
            $tampered = substr($encryptedData, 0, -5) . 'XXXXX';
            $this->cache->set($cacheKey, $tampered);

            // Retrieval should fail due to MAC verification
            expect(fn() => $this->stateManager->retrieve($componentId, SensitiveUserState::class))
                ->toThrow(\Exception::class);
        });
    });

    describe('Encryption with CSRF Protection', function () {
        it('encrypts state even when CSRF validation fails', function () {
            $componentId = 'secure-component:csrf-test';

            // Store state with encryption
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Verify encrypted in cache (CSRF failure doesn't expose data)
            $cacheKey = "livecomponent_state:{$componentId}";
            $cachedData = $this->cache->get($cacheKey);

            expect($cachedData)->not->toContain('sensitive-token-abc123');
            expect($cachedData)->not->toContain('sk_live_secret123');
        });

        it('maintains encryption through CSRF token rotation', function () {
            $componentId = 'secure-component:csrf-rotation';

            // Store state
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Simulate CSRF token rotation (state should remain encrypted)
            $retrieved1 = $this->stateManager->retrieve($componentId, SensitiveUserState::class);

            // Re-store with potentially new CSRF token
            $this->stateManager->store($componentId, $retrieved1);

            // Should still decrypt correctly
            $retrieved2 = $this->stateManager->retrieve($componentId, SensitiveUserState::class);

            expect($retrieved2->sessionToken)->toBe('sensitive-token-abc123');
        });
    });

    describe('Encryption with Rate Limiting', function () {
        it('maintains encrypted state during rate limit enforcement', function () {
            $componentId = 'secure-component:rate-limit';

            // Store sensitive state
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Simulate multiple requests (rate limiting scenario)
            for ($i = 0; $i < 5; $i++) {
                $retrieved = $this->stateManager->retrieve($componentId, SensitiveUserState::class);

                // Each retrieval should decrypt correctly
                expect($retrieved->sessionToken)->toBe('sensitive-token-abc123');
            }

            // State should remain encrypted in cache
            $cacheKey = "livecomponent_state:{$componentId}";
            $cachedData = $this->cache->get($cacheKey);
            expect($cachedData)->not->toContain('sensitive-token-abc123');
        });

        it('does not leak sensitive data in rate limit errors', function () {
            $componentId = 'secure-component:rate-limit-error';

            // Store state
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Rate limit should not expose encrypted state
            $cacheKey = "livecomponent_state:{$componentId}";
            $encryptedData = $this->cache->get($cacheKey);

            // Error messages should not contain plaintext
            expect($encryptedData)->not->toContain('sensitive-token-abc123');
            expect($encryptedData)->not->toContain('api_key');
        });
    });

    describe('Encryption with Idempotency', function () {
        it('encrypts idempotent cached results', function () {
            $componentId = 'secure-component:idempotency';
            $idempotencyKey = 'idem-key-' . uniqid();

            // Store state
            $this->stateManager->store($componentId, $this->sensitiveState);

            // First execution - cache result
            $result1 = $this->stateManager->retrieve($componentId, SensitiveUserState::class);

            // Simulate idempotency caching
            $idempotencyCacheKey = "idempotency:{$idempotencyKey}";
            $this->cache->set($idempotencyCacheKey, $result1->toArray());

            // Cached idempotent result should not expose sensitive data
            $cachedResult = $this->cache->get($idempotencyCacheKey);
            // Note: Idempotency cache stores unencrypted results
            // This is intentional - idempotency is short-lived and action-specific
            expect($cachedResult)->toBeArray();
        });

        it('retrieves encrypted state for idempotent requests', function () {
            $componentId = 'secure-component:idempotent-retrieval';
            $idempotencyKey = 'idem-' . uniqid();

            // Store encrypted state
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Multiple idempotent retrievals
            for ($i = 0; $i < 3; $i++) {
                $retrieved = $this->stateManager->retrieve($componentId, SensitiveUserState::class);
                expect($retrieved->sessionToken)->toBe('sensitive-token-abc123');
            }

            // State remains encrypted
            $cacheKey = "livecomponent_state:{$componentId}";
            $cachedData = $this->cache->get($cacheKey);
            expect($cachedData)->not->toContain('sensitive-token-abc123');
        });
    });

    describe('Complete Security Stack Integration', function () {
        it('enforces all 5 security layers with encrypted state', function () {
            $componentId = 'secure-component:full-stack';

            // 1. Store with encryption (Layer 5)
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Verify encryption
            $cacheKey = "livecomponent_state:{$componentId}";
            $encryptedData = $this->cache->get($cacheKey);
            expect($encryptedData)->not->toContain('sensitive-token-abc123');

            // 2. Retrieve (decrypts automatically via transformer)
            $retrieved = $this->stateManager->retrieve($componentId, SensitiveUserState::class);

            // 3. Verify decryption (all sensitive data intact)
            expect($retrieved->userId)->toBe('user-123');
            expect($retrieved->sessionToken)->toBe('sensitive-token-abc123');
            expect($retrieved->privateData['ssn'])->toBe('123-45-6789');

            // 4. Update state (re-encrypts on store)
            $updatedState = new SensitiveUserState(
                userId: $retrieved->userId,
                email: $retrieved->email,
                sessionToken: 'new-token-xyz789',
                privateData: $retrieved->privateData
            );

            $this->stateManager->store($componentId, $updatedState);

            // 5. Verify new state is encrypted
            $newEncryptedData = $this->cache->get($cacheKey);
            expect($newEncryptedData)->not->toContain('new-token-xyz789');
            expect($newEncryptedData)->not->toBe($encryptedData); // Different encryption

            // 6. Final retrieval verification
            $final = $this->stateManager->retrieve($componentId, SensitiveUserState::class);
            expect($final->sessionToken)->toBe('new-token-xyz789');
        });

        it('security validation order: CSRF -> Rate Limit -> Idempotency -> Authorization -> Encryption', function () {
            $componentId = 'secure-component:validation-order';

            // Layer 5: Encryption (happens on store/retrieve)
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Verify encryption layer active
            $cacheKey = "livecomponent_state:{$componentId}";
            $encryptedData = $this->cache->get($cacheKey);
            expect($encryptedData)->toBeString();
            expect($encryptedData)->not->toContain('sensitive-token-abc123');

            // Layers 1-4 would be validated before action execution
            // Layer 5 (Encryption) protects state at rest regardless of validation results

            // Even if validation fails, state remains encrypted
            $retrieved = $this->stateManager->retrieve($componentId, SensitiveUserState::class);
            expect($retrieved->sessionToken)->toBe('sensitive-token-abc123');
        });
    });

    describe('Performance with Encryption', function () {
        it('maintains acceptable performance with encryption overhead', function () {
            $componentId = 'secure-component:performance';

            // Measure encryption overhead
            $startTime = microtime(true);

            for ($i = 0; $i < 100; $i++) {
                $this->stateManager->store($componentId, $this->sensitiveState);
                $this->stateManager->retrieve($componentId, SensitiveUserState::class);
            }

            $duration = microtime(true) - $startTime;
            $avgTimeMs = ($duration / 100) * 1000;

            // Encryption overhead should be < 2ms per operation (as per docs)
            expect($avgTimeMs)->toBeLessThan(2.0);
        });

        it('handles large encrypted states efficiently', function () {
            // Large private data
            $largePrivateData = [];
            for ($i = 0; $i < 100; $i++) {
                $largePrivateData["key_{$i}"] = str_repeat('sensitive-data-', 10);
            }

            $largeState = new SensitiveUserState(
                userId: 'user-large',
                email: 'large@example.com',
                sessionToken: 'large-token',
                privateData: $largePrivateData
            );

            $componentId = 'secure-component:large-state';

            $startTime = microtime(true);
            $this->stateManager->store($componentId, $largeState);
            $retrieved = $this->stateManager->retrieve($componentId, SensitiveUserState::class);
            $duration = (microtime(true) - $startTime) * 1000;

            // Should handle large state in < 5ms
            expect($duration)->toBeLessThan(5.0);
            expect(count($retrieved->privateData))->toBe(100);
        });
    });

    describe('Error Handling with Encryption', function () {
        it('throws StateEncryptionException on decryption failure', function () {
            $componentId = 'secure-component:decrypt-error';

            // Store valid encrypted state
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Corrupt the encrypted data
            $cacheKey = "livecomponent_state:{$componentId}";
            $this->cache->set($cacheKey, 'corrupted-data');

            // Should throw StateEncryptionException
            expect(fn() => $this->stateManager->retrieve($componentId, SensitiveUserState::class))
                ->toThrow(\Exception::class);
        });

        it('does not leak sensitive data in error messages', function () {
            $componentId = 'secure-component:error-leak';

            try {
                // Attempt to retrieve non-existent encrypted state
                $this->stateManager->retrieve($componentId, SensitiveUserState::class);
            } catch (\Exception $e) {
                // Error message should not contain any encryption key info
                expect($e->getMessage())->not->toContain(bin2hex($this->encryptionKey));
            }
        });

        it('maintains encryption even when state retrieval fails', function () {
            $componentId = 'secure-component:retrieval-fail';

            // Store valid state
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Clear cache to simulate retrieval failure
            $this->cache->clear();

            // Re-store (should encrypt again)
            $this->stateManager->store($componentId, $this->sensitiveState);

            // Verify encryption
            $cacheKey = "livecomponent_state:{$componentId}";
            $encryptedData = $this->cache->get($cacheKey);
            expect($encryptedData)->not->toContain('sensitive-token-abc123');
        });
    });

    describe('Multi-Component Encryption Isolation', function () {
        it('encrypts each component state independently', function () {
            $componentId1 = 'secure-component:isolation-1';
            $componentId2 = 'secure-component:isolation-2';

            $state1 = new SensitiveUserState(
                userId: 'user-1',
                email: 'user1@example.com',
                sessionToken: 'token-1',
                privateData: ['key' => 'value1']
            );

            $state2 = new SensitiveUserState(
                userId: 'user-2',
                email: 'user2@example.com',
                sessionToken: 'token-2',
                privateData: ['key' => 'value2']
            );

            // Store both
            $this->stateManager->store($componentId1, $state1);
            $this->stateManager->store($componentId2, $state2);

            // Each should have different encrypted data (unique nonces)
            $encrypted1 = $this->cache->get("livecomponent_state:{$componentId1}");
            $encrypted2 = $this->cache->get("livecomponent_state:{$componentId2}");

            expect($encrypted1)->not->toBe($encrypted2);

            // Each should decrypt to correct state
            $retrieved1 = $this->stateManager->retrieve($componentId1, SensitiveUserState::class);
            $retrieved2 = $this->stateManager->retrieve($componentId2, SensitiveUserState::class);

            expect($retrieved1->userId)->toBe('user-1');
            expect($retrieved2->userId)->toBe('user-2');
        });
    });
});
