<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Infrastructure\Cache;

use App\Scolarite\Domain\Model\Evaluation\ClassStatistics;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
use App\Scolarite\Infrastructure\Cache\CachingEvaluationStatisticsRepository;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryEvaluationStatisticsRepository;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

final class CachingEvaluationStatisticsRepositoryTest extends TestCase
{
    private InMemoryEvaluationStatisticsRepository $inner;
    private CachingEvaluationStatisticsRepository $cached;

    protected function setUp(): void
    {
        $this->inner = new InMemoryEvaluationStatisticsRepository();
        $this->cached = new CachingEvaluationStatisticsRepository(
            inner: $this->inner,
            cache: new ArrayAdapter(),
        );
    }

    #[Test]
    public function itCachesStatisticsOnRead(): void
    {
        $evaluationId = EvaluationId::generate();
        $stats = new ClassStatistics(average: 14.5, min: 8.0, max: 19.0, median: 15.0, gradedCount: 5);

        $this->inner->save($evaluationId, $stats);

        // Premier appel : va au inner
        $result1 = $this->cached->findByEvaluation($evaluationId);
        self::assertNotNull($result1);
        self::assertSame(14.5, $result1->average);

        // Deuxième appel : devrait venir du cache (même résultat)
        $result2 = $this->cached->findByEvaluation($evaluationId);
        self::assertNotNull($result2);
        self::assertSame(14.5, $result2->average);
    }

    #[Test]
    public function itInvalidatesCacheOnSave(): void
    {
        $evaluationId = EvaluationId::generate();
        $stats1 = new ClassStatistics(average: 14.0, min: 8.0, max: 19.0, median: 14.0, gradedCount: 3);

        // Sauvegarder et lire pour remplir le cache
        $this->cached->save($evaluationId, $stats1);
        $this->cached->findByEvaluation($evaluationId);

        // Mettre à jour
        $stats2 = new ClassStatistics(average: 16.0, min: 10.0, max: 20.0, median: 16.0, gradedCount: 4);
        $this->cached->save($evaluationId, $stats2);

        $result = $this->cached->findByEvaluation($evaluationId);
        self::assertNotNull($result);
        self::assertSame(16.0, $result->average);
    }

    #[Test]
    public function itReturnsNullForUnknownEvaluation(): void
    {
        $result = $this->cached->findByEvaluation(EvaluationId::generate());
        self::assertNull($result);
    }

    #[Test]
    public function itInvalidatesCacheOnDelete(): void
    {
        $evaluationId = EvaluationId::generate();
        $stats = new ClassStatistics(average: 14.5, min: 8.0, max: 19.0, median: 15.0, gradedCount: 5);

        $this->cached->save($evaluationId, $stats);

        // Remplir le cache
        $this->cached->findByEvaluation($evaluationId);

        // Supprimer
        $this->cached->delete($evaluationId);

        // Le cache ne doit plus retourner l'ancienne valeur
        self::assertNull($this->cached->findByEvaluation($evaluationId));
    }

    #[Test]
    public function itCachesNullResultForUnknownEvaluation(): void
    {
        $evaluationId = EvaluationId::generate();

        // Premier appel : null → mis en cache
        self::assertNull($this->cached->findByEvaluation($evaluationId));

        // Sauvegarder directement dans inner (sans passer par le cache)
        $this->inner->save($evaluationId, new ClassStatistics(
            average: 12.0,
            min: 10.0,
            max: 14.0,
            median: 12.0,
            gradedCount: 2,
        ));

        // Le cache retourne encore null (valeur cachée)
        $result = $this->cached->findByEvaluation($evaluationId);
        self::assertNull($result);
    }
}
