<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\Cache;

use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Cache\Driver\InMemoryCache;
use App\Framework\Cache\GeneralCache;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Serializer\Php\PhpSerializer;

test('cache can store and retrieve values', function () {
    $cache = new GeneralCache(new InMemoryCache(), new PhpSerializer());
    $key = CacheKey::fromString('test-key');

    $result = $cache->set(CacheItem::forSet($key, 'test-value'));
    expect($result)->toBeTrue();

    $resultItem = $cache->get($key);
    expect($resultItem->isHit)->toBeTrue();
    expect($resultItem->value)->toBe('test-value');
});

test('cache returns miss for non-existent key', function () {
    $cache = new GeneralCache(new InMemoryCache(), new PhpSerializer());
    $key = CacheKey::fromString('non-existent');

    $item = $cache->get($key);
    expect($item->isHit)->toBeFalse();
    expect($item->value)->toBeNull();
});

test('cache can check if key exists', function () {
    $cache = new GeneralCache(new InMemoryCache(), new PhpSerializer());
    $key = CacheKey::fromString('test-key');

    $hasResult = $cache->has($key);
    expect($hasResult[$key->toString()])->toBeFalse();

    $cache->set(CacheItem::forSet($key, 'value'));
    $hasResult = $cache->has($key);
    expect($hasResult[$key->toString()])->toBeTrue();
});

test('cache can forget keys', function () {
    $cache = new GeneralCache(new InMemoryCache(), new PhpSerializer());
    $key = CacheKey::fromString('test-key');

    $cache->set(CacheItem::forSet($key, 'value'));
    $hasResult = $cache->has($key);
    expect($hasResult[$key->toString()])->toBeTrue();

    $result = $cache->forget($key);
    expect($result)->toBeTrue();
    $hasResult = $cache->has($key);
    expect($hasResult[$key->toString()])->toBeFalse();
});

test('cache can clear all entries', function () {
    $cache = new GeneralCache(new InMemoryCache(), new PhpSerializer());
    $key1 = CacheKey::fromString('key1');
    $key2 = CacheKey::fromString('key2');

    $cache->set(CacheItem::forSet($key1, 'value1'));
    $cache->set(CacheItem::forSet($key2, 'value2'));

    $result = $cache->clear();
    expect($result)->toBeTrue();

    $hasResult1 = $cache->has($key1);
    $hasResult2 = $cache->has($key2);
    expect($hasResult1[$key1->toString()])->toBeFalse();
    expect($hasResult2[$key2->toString()])->toBeFalse();
});

test('cache remember pattern works', function () {
    $cache = new GeneralCache(new InMemoryCache(), new PhpSerializer());
    $key = CacheKey::fromString('test-key');
    $callCount = 0;

    $callback = function () use (&$callCount) {
        $callCount++;

        return 'computed-value';
    };

    // First call should execute callback
    $item1 = $cache->remember($key, $callback);
    expect($item1->value)->toBe('computed-value');
    expect($callCount)->toBe(1);

    // Second call should return cached value
    $item2 = $cache->remember($key, $callback);
    expect($item2->value)->toBe('computed-value');
    expect($callCount)->toBe(1); // Callback not called again
});

test('cache respects TTL', function () {
    $cache = new GeneralCache(new InMemoryCache(), new PhpSerializer());
    $key = CacheKey::fromString('test-key');
    $ttl = Duration::fromSeconds(60);

    // This test would need a mock or a way to advance time
    // For now, just test that TTL parameter is accepted
    $result = $cache->set(CacheItem::forSet($key, 'value', $ttl));
    expect($result)->toBeTrue();
});

test('cache can store different data types', function () {
    $cache = new GeneralCache(new InMemoryCache(), new PhpSerializer());

    // String
    $stringKey = CacheKey::fromString('string');
    $cache->set(CacheItem::forSet($stringKey, 'test'));
    expect($cache->get($stringKey)->value)->toBe('test');

    // Integer
    $intKey = CacheKey::fromString('int');
    $cache->set(CacheItem::forSet($intKey, 42));
    expect($cache->get($intKey)->value)->toBe(42);

    // Array
    $arrayKey = CacheKey::fromString('array');
    $cache->set(CacheItem::forSet($arrayKey, ['a' => 1, 'b' => 2]));
    expect($cache->get($arrayKey)->value)->toBe(['a' => 1, 'b' => 2]);

    // Object - stdClass becomes __PHP_Incomplete_Class when deserialized
    // This is expected PHP behavior with PhpSerializer
    $objectKey = CacheKey::fromString('object');
    $obj = (object) ['test' => 'value', 'number' => 42];
    $cache->set(CacheItem::forSet($objectKey, $obj));
    $result = $cache->get($objectKey);

    // Verify object was cached (becomes __PHP_Incomplete_Class on deserialization)
    expect($result->value)->toBeObject();
    expect($result->value::class)->toBe('__PHP_Incomplete_Class');
});
