<?php

declare(strict_types=1);

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\LiveComponents\Cache\ComponentStateCache;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentState;
use App\Framework\StateManagement\InMemoryStateManager;
use App\Framework\StateManagement\StateManagerStatistics;

describe('ComponentStateCache with StateManager', function () {
    beforeEach(function () {
        // Create StateManager for ComponentState
        $this->stateManager = InMemoryStateManager::for(ComponentState::class);
        $this->cache = new ComponentStateCache($this->stateManager);
        $this->componentId = ComponentId::create('counter', 'instance-1');
    });

    it('stores component state', function () {
        $state = ComponentState::fromArray([
            'count' => 5,
            'lastUpdated' => time()
        ]);

        $this->cache->store($this->componentId, $state);

        expect($this->cache->has($this->componentId))->toBeTrue();
    });

    it('retrieves stored component state', function () {
        $state = ComponentState::fromArray([
            'count' => 10,
            'label' => 'Counter'
        ]);

        $this->cache->store($this->componentId, $state);

        $retrieved = $this->cache->retrieve($this->componentId);

        expect($retrieved)->toBeInstanceOf(ComponentState::class);
        expect($retrieved->toArray()['count'])->toBe(10);
        expect($retrieved->toArray()['label'])->toBe('Counter');
    });

    it('returns null for non-existent state', function () {
        $nonExistentId = ComponentId::create('counter', 'does-not-exist');

        $retrieved = $this->cache->retrieve($nonExistentId);

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

    it('checks state existence', function () {
        $state = ComponentState::fromArray(['value' => 42]);

        expect($this->cache->has($this->componentId))->toBeFalse();

        $this->cache->store($this->componentId, $state);

        expect($this->cache->has($this->componentId))->toBeTrue();
    });

    it('invalidates cached state', function () {
        $state = ComponentState::fromArray(['value' => 100]);

        $this->cache->store($this->componentId, $state);
        expect($this->cache->has($this->componentId))->toBeTrue();

        $this->cache->invalidate($this->componentId);

        expect($this->cache->has($this->componentId))->toBeFalse();
        expect($this->cache->retrieve($this->componentId))->toBeNull();
    });

    it('clears all cached states', function () {
        $state1 = ComponentState::fromArray(['value' => 1]);
        $state2 = ComponentState::fromArray(['value' => 2]);

        $id1 = ComponentId::create('counter', 'instance-1');
        $id2 = ComponentId::create('counter', 'instance-2');

        $this->cache->store($id1, $state1);
        $this->cache->store($id2, $state2);

        $this->cache->clear();

        expect($this->cache->has($id1))->toBeFalse();
        expect($this->cache->has($id2))->toBeFalse();
    });

    it('atomically updates component state', function () {
        $initialState = ComponentState::fromArray([
            'count' => 5,
            'version' => 1
        ]);

        $this->cache->store($this->componentId, $initialState);

        $updatedState = $this->cache->update(
            $this->componentId,
            function ($state) {
                if ($state === null) {
                    return ComponentState::fromArray(['count' => 1, 'version' => 1]);
                }

                $data = $state->toArray();
                return ComponentState::fromArray([
                    'count' => $data['count'] + 1,
                    'version' => $data['version'] + 1
                ]);
            }
        );

        expect($updatedState->toArray()['count'])->toBe(6);
        expect($updatedState->toArray()['version'])->toBe(2);
    });

    it('handles null state in atomic update', function () {
        $newState = $this->cache->update(
            $this->componentId,
            function ($state) {
                return $state ?? ComponentState::fromArray([
                    'count' => 1,
                    'initialized' => true
                ]);
            }
        );

        expect($newState->toArray()['count'])->toBe(1);
        expect($newState->toArray()['initialized'])->toBe(true);
    });

    it('respects custom TTL', function () {
        $state = ComponentState::fromArray(['value' => 42]);
        $customTtl = Duration::fromMinutes(30);

        $this->cache->store($this->componentId, $state, $customTtl);

        expect($this->cache->has($this->componentId))->toBeTrue();
    });

    it('uses default TTL when not specified', function () {
        $state = ComponentState::fromArray(['value' => 100]);

        $this->cache->store($this->componentId, $state);

        expect($this->cache->has($this->componentId))->toBeTrue();
    });

    it('provides cache statistics', function () {
        $state = ComponentState::fromArray(['value' => 1]);

        $this->cache->store($this->componentId, $state);
        $this->cache->retrieve($this->componentId);
        $this->cache->has($this->componentId);

        $stats = $this->cache->getStats();

        expect($stats)->toBeInstanceOf(StateManagerStatistics::class);
        expect($stats->totalKeys)->toBeGreaterThanOrEqual(1);
        expect($stats->setCount)->toBeGreaterThanOrEqual(1);
        expect($stats->hitCount)->toBeGreaterThanOrEqual(1);
    });

    it('stores with automatic TTL based on component type', function () {
        $state = ComponentState::fromArray(['value' => 42]);

        $this->cache->storeWithAutoTTL($this->componentId, $state, 'counter');

        expect($this->cache->has($this->componentId))->toBeTrue();
    });

    it('applies different TTLs for different component types', function () {
        $timerState = ComponentState::fromArray(['seconds' => 30]);
        $chartState = ComponentState::fromArray(['data' => [1, 2, 3]]);
        $cardState = ComponentState::fromArray(['title' => 'Card Title']);

        $timerId = ComponentId::create('timer', 'instance-1');
        $chartId = ComponentId::create('chart', 'instance-1');
        $cardId = ComponentId::create('card', 'instance-1');

        // timer: 5 minutes TTL
        $this->cache->storeWithAutoTTL($timerId, $timerState, 'timer');

        // chart: 30 minutes TTL
        $this->cache->storeWithAutoTTL($chartId, $chartState, 'chart');

        // card: 2 hours TTL
        $this->cache->storeWithAutoTTL($cardId, $cardState, 'card');

        expect($this->cache->has($timerId))->toBeTrue();
        expect($this->cache->has($chartId))->toBeTrue();
        expect($this->cache->has($cardId))->toBeTrue();
    });

    it('uses default TTL for unknown component types', function () {
        $state = ComponentState::fromArray(['value' => 123]);
        $unknownId = ComponentId::create('unknown-type', 'instance-1');

        $this->cache->storeWithAutoTTL($unknownId, $state, 'unknown-type');

        expect($this->cache->has($unknownId))->toBeTrue();
    });

    it('generates consistent cache keys for same component', function () {
        $state = ComponentState::fromArray(['value' => 42]);

        $id1 = ComponentId::create('counter', 'instance-1');
        $id2 = ComponentId::create('counter', 'instance-1');

        $this->cache->store($id1, $state);

        // Same component ID should find the cached state
        expect($this->cache->has($id2))->toBeTrue();
        expect($this->cache->retrieve($id2))->not->toBeNull();
    });

    it('generates different cache keys for different instances', function () {
        $state1 = ComponentState::fromArray(['value' => 1]);
        $state2 = ComponentState::fromArray(['value' => 2]);

        $id1 = ComponentId::create('counter', 'instance-1');
        $id2 = ComponentId::create('counter', 'instance-2');

        $this->cache->store($id1, $state1);
        $this->cache->store($id2, $state2);

        $retrieved1 = $this->cache->retrieve($id1);
        $retrieved2 = $this->cache->retrieve($id2);

        expect($retrieved1->toArray()['value'])->toBe(1);
        expect($retrieved2->toArray()['value'])->toBe(2);
    });

    it('handles complex state data', function () {
        $complexState = ComponentState::fromArray([
            'user' => [
                'id' => 123,
                'name' => 'John Doe',
                'email' => 'john@example.com'
            ],
            'preferences' => [
                'theme' => 'dark',
                'notifications' => true
            ],
            'metadata' => [
                'created_at' => time(),
                'updated_at' => time()
            ]
        ]);

        $this->cache->store($this->componentId, $complexState);

        $retrieved = $this->cache->retrieve($this->componentId);

        expect($retrieved->toArray()['user']['name'])->toBe('John Doe');
        expect($retrieved->toArray()['preferences']['theme'])->toBe('dark');
        expect($retrieved->toArray()['metadata'])->toHaveKey('created_at');
    });

    it('preserves state through multiple updates', function () {
        $initialState = ComponentState::fromArray(['count' => 0]);

        $this->cache->store($this->componentId, $initialState);

        // Update 1
        $this->cache->update(
            $this->componentId,
            fn($state) => ComponentState::fromArray(['count' => $state->toArray()['count'] + 1])
        );

        // Update 2
        $this->cache->update(
            $this->componentId,
            fn($state) => ComponentState::fromArray(['count' => $state->toArray()['count'] + 1])
        );

        // Update 3
        $finalState = $this->cache->update(
            $this->componentId,
            fn($state) => ComponentState::fromArray(['count' => $state->toArray()['count'] + 1])
        );

        expect($finalState->toArray()['count'])->toBe(3);
    });

    it('correctly reports statistics after various operations', function () {
        $state = ComponentState::fromArray(['value' => 1]);

        // Set operations
        $this->cache->store($this->componentId, $state);
        $this->cache->store($this->componentId, $state);

        // Get operations
        $this->cache->retrieve($this->componentId);
        $this->cache->retrieve($this->componentId);

        // Has operations
        $this->cache->has($this->componentId);

        // Update operation
        $this->cache->update(
            $this->componentId,
            fn($s) => ComponentState::fromArray(['value' => 2])
        );

        $stats = $this->cache->getStats();

        expect($stats->setCount)->toBeGreaterThanOrEqual(2);
        expect($stats->hitCount)->toBeGreaterThanOrEqual(2);
        expect($stats->updateCount)->toBeGreaterThanOrEqual(1);
    });
});
