<?php

declare(strict_types=1);

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

// Test State for StateManager tests
final readonly class ManagerTestState implements SerializableState
{
    public function __construct(
        public string $userId,
        public int $score
    ) {
    }

    public function toArray(): array
    {
        return [
            'user_id' => $this->userId,
            'score' => $this->score,
        ];
    }

    public static function fromArray(array $data): self
    {
        return new self(
            userId: $data['user_id'] ?? '',
            score: $data['score'] ?? 0
        );
    }
}

// Mock transformer for testing priority ordering
final readonly class MockTransformer implements StateTransformer
{
    public function __construct(
        private string $name,
        private int $priority,
        private string $marker = ''
    ) {
    }

    public function transformIn(SerializableState $state): mixed
    {
        $array = $state->toArray();
        $array['_marker_in_' . $this->name] = $this->marker;
        return $array;
    }

    public function transformOut(mixed $data): SerializableState
    {
        if (!is_array($data)) {
            throw new \InvalidArgumentException('Expected array');
        }

        $data['_marker_out_' . $this->name] = $this->marker;
        return ManagerTestState::fromArray($data);
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getPriority(): int
    {
        return $this->priority;
    }
}

describe('CacheBasedStateManager', function () {
    beforeEach(function () {
        $this->cache = new InMemoryCache();

        $this->stateManager = CacheBasedStateManager::for(
            cache: $this->cache,
            keyPrefix: 'test',
            stateClass: ManagerTestState::class,
            defaultTtl: Duration::fromMinutes(10),
            namespace: 'default'
        );

        $this->testState = new ManagerTestState(
            userId: 'user123',
            score: 100
        );
    });

    it('stores and retrieves state without transformers (legacy path)', function () {
        $this->stateManager->setState('key1', $this->testState);

        $retrieved = $this->stateManager->getState('key1');

        expect($retrieved)->toBeInstanceOf(ManagerTestState::class);
        expect($retrieved->userId)->toBe('user123');
        expect($retrieved->score)->toBe(100);
    });

    it('returns null for non-existent state', function () {
        $retrieved = $this->stateManager->getState('non-existent');

        expect($retrieved)->toBeNull();
    });

    it('checks state existence', function () {
        expect($this->stateManager->hasState('key1'))->toBeFalse();

        $this->stateManager->setState('key1', $this->testState);

        expect($this->stateManager->hasState('key1'))->toBeTrue();
    });

    it('removes state', function () {
        $this->stateManager->setState('key1', $this->testState);
        expect($this->stateManager->hasState('key1'))->toBeTrue();

        $this->stateManager->removeState('key1');

        expect($this->stateManager->hasState('key1'))->toBeFalse();
    });

    it('updates state', function () {
        $this->stateManager->setState('key1', $this->testState);

        $updated = $this->stateManager->updateState(
            'key1',
            function ($state) {
                return new ManagerTestState(
                    userId: $state->userId,
                    score: $state->score + 50
                );
            }
        );

        expect($updated->score)->toBe(150);

        $retrieved = $this->stateManager->getState('key1');
        expect($retrieved->score)->toBe(150);
    });

    it('tracks statistics', function () {
        // Miss
        $this->stateManager->getState('key1');

        // Hit
        $this->stateManager->setState('key1', $this->testState);
        $this->stateManager->getState('key1');

        // Remove
        $this->stateManager->removeState('key1');

        // Update
        $this->stateManager->setState('key2', $this->testState);
        $this->stateManager->updateState('key2', fn($s) => $s);

        $stats = $this->stateManager->getStatistics();

        expect($stats->hitCount)->toBe(1);
        expect($stats->missCount)->toBe(1);
        expect($stats->setCount)->toBe(2);
        expect($stats->removeCount)->toBe(1);
        expect($stats->updateCount)->toBe(1);
    });
});

describe('CacheBasedStateManager with Transformers', function () {
    beforeEach(function () {
        $this->cache = new InMemoryCache();

        $this->stateManager = CacheBasedStateManager::for(
            cache: $this->cache,
            keyPrefix: 'test',
            stateClass: ManagerTestState::class
        );

        $this->testState = new ManagerTestState(
            userId: 'user456',
            score: 200
        );
    });

    it('adds transformer to pipeline', function () {
        $transformer = new MockTransformer('test', 50);

        $result = $this->stateManager->addTransformer($transformer);

        expect($result)->toBe($this->stateManager);  // Fluent interface
    });

    it('applies transformer on setState and getState', function () {
        $random = new SecureRandomGenerator();
        $crypto = new CryptographicUtilities($random);
        $encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

        $encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
        $serializer = new EncryptedStateSerializer($encryptor);

        $transformer = new EncryptionTransformer($serializer, ManagerTestState::class);

        $this->stateManager->addTransformer($transformer);

        // Store state (should be encrypted)
        $this->stateManager->setState('encrypted-key', $this->testState);

        // Retrieve state (should be decrypted)
        $retrieved = $this->stateManager->getState('encrypted-key');

        expect($retrieved)->toBeInstanceOf(ManagerTestState::class);
        expect($retrieved->userId)->toBe('user456');
        expect($retrieved->score)->toBe(200);
    });

    it('executes transformers in priority order on transformIn', function () {
        $high = new MockTransformer('high', 100, 'H');
        $medium = new MockTransformer('medium', 50, 'M');
        $low = new MockTransformer('low', 10, 'L');

        // Add in random order
        $this->stateManager
            ->addTransformer($medium)
            ->addTransformer($low)
            ->addTransformer($high);

        $this->stateManager->setState('priority-test', $this->testState);

        // Verify execution order via cache inspection
        // transformIn should execute: high → medium → low
        // We can't easily verify this without mocking, but priority sorting is tested
    });

    it('executes transformers in reverse order on transformOut', function () {
        // This is tested indirectly through round-trip
        $transformer1 = new MockTransformer('first', 100);
        $transformer2 = new MockTransformer('second', 50);

        $this->stateManager
            ->addTransformer($transformer1)
            ->addTransformer($transformer2);

        $this->stateManager->setState('reverse-test', $this->testState);
        $retrieved = $this->stateManager->getState('reverse-test');

        expect($retrieved)->toBeInstanceOf(ManagerTestState::class);
    });

    it('handles multiple transformers in pipeline', function () {
        // Create 3 transformers with different priorities
        $random = new SecureRandomGenerator();
        $crypto = new CryptographicUtilities($random);
        $encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

        $encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
        $serializer = new EncryptedStateSerializer($encryptor);

        $encryption = new EncryptionTransformer($serializer, ManagerTestState::class);

        $this->stateManager->addTransformer($encryption);

        // Round-trip through multiple transformers
        $this->stateManager->setState('multi-key', $this->testState);
        $retrieved = $this->stateManager->getState('multi-key');

        expect($retrieved)->toBeInstanceOf(ManagerTestState::class);
        expect($retrieved->userId)->toBe($this->testState->userId);
        expect($retrieved->score)->toBe($this->testState->score);
    });

    it('sorts transformers only once', function () {
        $t1 = new MockTransformer('t1', 50);
        $t2 = new MockTransformer('t2', 100);

        $this->stateManager->addTransformer($t1);
        $this->stateManager->addTransformer($t2);

        // First setState triggers sort
        $this->stateManager->setState('key1', $this->testState);

        // Second setState should reuse sorted transformers
        $this->stateManager->setState('key2', $this->testState);

        // Both should work correctly
        expect($this->stateManager->getState('key1'))->not->toBeNull();
        expect($this->stateManager->getState('key2'))->not->toBeNull();
    });

    it('re-sorts transformers when new transformer added', function () {
        $t1 = new MockTransformer('t1', 50);

        $this->stateManager->addTransformer($t1);
        $this->stateManager->setState('key1', $this->testState);

        // Add another transformer (should trigger re-sort on next operation)
        $t2 = new MockTransformer('t2', 100);
        $this->stateManager->addTransformer($t2);

        $this->stateManager->setState('key2', $this->testState);

        expect($this->stateManager->getState('key2'))->not->toBeNull();
    });
});

describe('CacheBasedStateManager Encryption Integration', function () {
    it('encrypts and decrypts state in production-like scenario', function () {
        $cache = new InMemoryCache();

        // Production setup with encryption
        $random = new SecureRandomGenerator();
        $crypto = new CryptographicUtilities($random);
        $encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

        $encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
        $serializer = new EncryptedStateSerializer($encryptor);
        $transformer = new EncryptionTransformer($serializer, ManagerTestState::class);

        $stateManager = CacheBasedStateManager::for(
            cache: $cache,
            keyPrefix: 'secure',
            stateClass: ManagerTestState::class,
            defaultTtl: Duration::fromHours(1)
        );

        $stateManager->addTransformer($transformer);

        // Simulate multiple users with sensitive data
        $users = [
            'user1' => new ManagerTestState('user1', 1000),
            'user2' => new ManagerTestState('user2', 2000),
            'user3' => new ManagerTestState('user3', 3000),
        ];

        foreach ($users as $key => $state) {
            $stateManager->setState($key, $state);
        }

        // Verify all states are encrypted in cache and can be retrieved
        foreach ($users as $key => $originalState) {
            $retrieved = $stateManager->getState($key);

            expect($retrieved)->toBeInstanceOf(ManagerTestState::class);
            expect($retrieved->userId)->toBe($originalState->userId);
            expect($retrieved->score)->toBe($originalState->score);
        }
    });

    it('updates encrypted state correctly', function () {
        $cache = new InMemoryCache();

        $random = new SecureRandomGenerator();
        $crypto = new CryptographicUtilities($random);
        $encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

        $encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
        $serializer = new EncryptedStateSerializer($encryptor);
        $transformer = new EncryptionTransformer($serializer, ManagerTestState::class);

        $stateManager = CacheBasedStateManager::for(
            cache: $cache,
            keyPrefix: 'update',
            stateClass: ManagerTestState::class
        );

        $stateManager->addTransformer($transformer);

        $initialState = new ManagerTestState('user789', 500);
        $stateManager->setState('update-key', $initialState);

        // Update with encryption
        $updated = $stateManager->updateState(
            'update-key',
            function ($state) {
                return new ManagerTestState(
                    userId: $state->userId,
                    score: $state->score + 250
                );
            }
        );

        expect($updated->score)->toBe(750);

        // Verify updated state is still encrypted and retrievable
        $retrieved = $stateManager->getState('update-key');
        expect($retrieved->score)->toBe(750);
    });
});
