<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Infrastructure\Middleware;

use App\Administration\Application\Dto\PaginatedResult;
use App\Administration\Application\Service\Cache\PaginatedQueryCache;
use App\Administration\Domain\Event\CompteActive;
use App\Administration\Domain\Event\InvitationParentActivee;
use App\Administration\Domain\Event\InvitationParentEnvoyee;
use App\Administration\Domain\Event\InvitationRenvoyee;
use App\Administration\Domain\Event\UtilisateurBloque;
use App\Administration\Domain\Event\UtilisateurInvite;
use App\Administration\Domain\Model\Invitation\ParentInvitationId;
use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Infrastructure\Middleware\PaginatedCacheInvalidationMiddleware;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Ramsey\Uuid\Uuid;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\StackInterface;
use Symfony\Component\Messenger\Stamp\ReceivedStamp;

final class PaginatedCacheInvalidationMiddlewareTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';

    private PaginatedQueryCache $cache;
    private PaginatedCacheInvalidationMiddleware $middleware;

    protected function setUp(): void
    {
        $this->cache = new PaginatedQueryCache(
            new TagAwareAdapter(new ArrayAdapter()),
        );
        $this->middleware = new PaginatedCacheInvalidationMiddleware($this->cache);
    }

    #[Test]
    public function invalidatesUsersCacheOnUtilisateurInvite(): void
    {
        $this->warmCache('users', self::TENANT_ID);

        $event = new UtilisateurInvite(
            userId: UserId::generate(),
            email: 'test@example.com',
            role: 'ROLE_PROF',
            firstName: 'Jean',
            lastName: 'Dupont',
            tenantId: TenantId::fromString(self::TENANT_ID),
            occurredOn: new DateTimeImmutable(),
        );

        $this->middleware->handle(new Envelope($event), $this->createPassthroughStack());

        $this->assertCacheWasInvalidated('users', self::TENANT_ID);
    }

    #[Test]
    public function invalidatesUsersCacheOnCompteActive(): void
    {
        $this->warmCache('users', self::TENANT_ID);

        $event = new CompteActive(
            userId: Uuid::uuid4()->toString(),
            email: 'test@example.com',
            tenantId: TenantId::fromString(self::TENANT_ID),
            role: 'ROLE_PROF',
            occurredOn: new DateTimeImmutable(),
            aggregateId: Uuid::uuid4(),
        );

        $this->middleware->handle(new Envelope($event), $this->createPassthroughStack());

        $this->assertCacheWasInvalidated('users', self::TENANT_ID);
    }

    #[Test]
    public function invalidatesUsersCacheOnInvitationRenvoyee(): void
    {
        $this->warmCache('users', self::TENANT_ID);

        $event = new InvitationRenvoyee(
            userId: UserId::generate(),
            email: 'test@example.com',
            tenantId: TenantId::fromString(self::TENANT_ID),
            occurredOn: new DateTimeImmutable(),
        );

        $this->middleware->handle(new Envelope($event), $this->createPassthroughStack());

        $this->assertCacheWasInvalidated('users', self::TENANT_ID);
    }

    #[Test]
    public function invalidatesParentInvitationsCacheOnInvitationParentEnvoyee(): void
    {
        $this->warmCache('parent_invitations', self::TENANT_ID);

        $event = new InvitationParentEnvoyee(
            invitationId: ParentInvitationId::generate(),
            studentId: UserId::generate(),
            parentEmail: new Email('parent@example.com'),
            tenantId: TenantId::fromString(self::TENANT_ID),
            occurredOn: new DateTimeImmutable(),
        );

        $this->middleware->handle(new Envelope($event), $this->createPassthroughStack());

        $this->assertCacheWasInvalidated('parent_invitations', self::TENANT_ID);
    }

    #[Test]
    public function invalidatesParentInvitationsAndUsersCacheOnInvitationParentActivee(): void
    {
        $this->warmCache('parent_invitations', self::TENANT_ID);
        $this->warmCache('users', self::TENANT_ID);

        $event = new InvitationParentActivee(
            invitationId: ParentInvitationId::generate(),
            studentId: UserId::generate(),
            parentUserId: UserId::generate(),
            tenantId: TenantId::fromString(self::TENANT_ID),
            occurredOn: new DateTimeImmutable(),
        );

        $this->middleware->handle(new Envelope($event), $this->createPassthroughStack());

        $this->assertCacheWasInvalidated('parent_invitations', self::TENANT_ID);
        $this->assertCacheWasInvalidated('users', self::TENANT_ID);
    }

    #[Test]
    public function doesNotInvalidateOnReceivedStamp(): void
    {
        $this->warmCache('users', self::TENANT_ID);

        $event = new UtilisateurInvite(
            userId: UserId::generate(),
            email: 'test@example.com',
            role: 'ROLE_PROF',
            firstName: 'Jean',
            lastName: 'Dupont',
            tenantId: TenantId::fromString(self::TENANT_ID),
            occurredOn: new DateTimeImmutable(),
        );

        $envelope = new Envelope($event, [new ReceivedStamp('async')]);
        $this->middleware->handle($envelope, $this->createPassthroughStack());

        $this->assertCacheStillValid('users', self::TENANT_ID);
    }

    #[Test]
    public function doesNotInvalidateOnUnrelatedEvent(): void
    {
        $this->warmCache('users', self::TENANT_ID);

        $event = new UtilisateurBloque(
            userId: UserId::generate(),
            email: 'test@example.com',
            reason: 'test',
            tenantId: TenantId::fromString(self::TENANT_ID),
            occurredOn: new DateTimeImmutable(),
        );

        $this->middleware->handle(new Envelope($event), $this->createPassthroughStack());

        $this->assertCacheStillValid('users', self::TENANT_ID);
    }

    private function warmCache(string $entityType, string $tenantId): void
    {
        $this->cache->getOrLoad(
            $entityType,
            $tenantId,
            ['page' => 1, 'limit' => 30],
            static fn (): PaginatedResult => new PaginatedResult(items: ['original'], total: 1, page: 1, limit: 30),
        );
    }

    private function assertCacheWasInvalidated(string $entityType, string $tenantId): void
    {
        $result = $this->cache->getOrLoad(
            $entityType,
            $tenantId,
            ['page' => 1, 'limit' => 30],
            static fn (): PaginatedResult => new PaginatedResult(items: ['fresh'], total: 1, page: 1, limit: 30),
        );

        self::assertSame(['fresh'], $result->items, "Cache for {$entityType}/{$tenantId} should have been invalidated");
    }

    private function assertCacheStillValid(string $entityType, string $tenantId): void
    {
        $result = $this->cache->getOrLoad(
            $entityType,
            $tenantId,
            ['page' => 1, 'limit' => 30],
            static fn (): PaginatedResult => new PaginatedResult(items: ['fresh'], total: 1, page: 1, limit: 30),
        );

        self::assertSame(['original'], $result->items, "Cache for {$entityType}/{$tenantId} should still contain original data");
    }

    private function createPassthroughStack(): StackInterface
    {
        $stack = $this->createMock(StackInterface::class);
        $nextMiddleware = $this->createMock(\Symfony\Component\Messenger\Middleware\MiddlewareInterface::class);

        $stack->method('next')->willReturn($nextMiddleware);
        $nextMiddleware->method('handle')->willReturnCallback(
            static fn (Envelope $envelope): Envelope => $envelope,
        );

        return $stack;
    }
}
