<?php

declare(strict_types=1);

namespace App\Tests\Unit\Shared\Infrastructure\Audit;

use App\Shared\Domain\Tenant\TenantId;
use App\Shared\Infrastructure\Audit\AuditLogEntry;
use App\Shared\Infrastructure\Audit\AuditLogRepository;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Result;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Ramsey\Uuid\Uuid;

/**
 * @see Story 1.7 - T9: Requetes d'investigation
 */
final class AuditLogRepositoryTest extends TestCase
{
    private Connection&MockObject $connection;
    private AuditLogRepository $repository;

    protected function setUp(): void
    {
        $this->connection = $this->createMock(Connection::class);
        $this->repository = new AuditLogRepository($this->connection);
    }

    public function testFindByUserReturnsAuditLogEntries(): void
    {
        $userId = Uuid::uuid4();
        $tenantId = TenantId::generate();

        $rows = [
            $this->createRowData(),
            $this->createRowData(),
        ];

        $queryBuilder = $this->createQueryBuilderMock($rows);
        $this->connection->method('createQueryBuilder')->willReturn($queryBuilder);

        $result = $this->repository->findByUser($userId, $tenantId);

        $this->assertCount(2, $result);
        $this->assertContainsOnlyInstancesOf(AuditLogEntry::class, $result);
    }

    public function testFindByUserAppliesFilters(): void
    {
        $userId = Uuid::uuid4();
        $tenantId = TenantId::generate();
        $from = new DateTimeImmutable('2026-01-01');
        $to = new DateTimeImmutable('2026-02-01');

        $queryBuilder = $this->createMock(QueryBuilder::class);
        $queryBuilder->method('select')->willReturnSelf();
        $queryBuilder->method('from')->willReturnSelf();
        $queryBuilder->method('where')->willReturnSelf();
        $queryBuilder->method('orderBy')->willReturnSelf();

        // Verify andWhere is called for each filter
        $queryBuilder->expects($this->atLeast(4))
            ->method('andWhere')
            ->willReturnSelf();

        // Verify setParameter is called with expected parameters
        $capturedParams = [];
        $queryBuilder->method('setParameter')
            ->willReturnCallback(static function (string $key, mixed $value) use ($queryBuilder, &$capturedParams) {
                $capturedParams[$key] = $value;

                return $queryBuilder;
            });

        // Verify pagination
        $queryBuilder->expects($this->once())
            ->method('setMaxResults')
            ->with(50)
            ->willReturnSelf();

        $queryBuilder->expects($this->once())
            ->method('setFirstResult')
            ->with(10)
            ->willReturnSelf();

        $result = $this->createMock(Result::class);
        $result->method('fetchAllAssociative')->willReturn([]);
        $queryBuilder->method('executeQuery')->willReturn($result);

        $this->connection->method('createQueryBuilder')->willReturn($queryBuilder);

        $this->repository->findByUser(
            $userId,
            $tenantId,
            $from,
            $to,
            'ConnexionReussie',
            50,
            10,
        );

        // Verify the parameters were captured
        $this->assertArrayHasKey('user_id', $capturedParams);
        $this->assertArrayHasKey('tenant_id', $capturedParams);
        $this->assertArrayHasKey('from', $capturedParams);
        $this->assertArrayHasKey('to', $capturedParams);
        $this->assertArrayHasKey('event_type', $capturedParams);
        $this->assertSame('ConnexionReussie', $capturedParams['event_type']);
    }

    public function testFindByResourceReturnsResults(): void
    {
        $aggregateId = Uuid::uuid4();
        $tenantId = TenantId::generate();

        $rows = [$this->createRowData()];

        $queryBuilder = $this->createQueryBuilderMock($rows);
        $this->connection->method('createQueryBuilder')->willReturn($queryBuilder);

        $result = $this->repository->findByResource('Note', $aggregateId, $tenantId);

        $this->assertCount(1, $result);
    }

    public function testFindByCorrelationIdReturnsResults(): void
    {
        $correlationId = Uuid::uuid4()->toString();
        $tenantId = TenantId::generate();

        $rows = [$this->createRowData()];

        $queryBuilder = $this->createQueryBuilderMock($rows);
        $this->connection->method('createQueryBuilder')->willReturn($queryBuilder);

        $result = $this->repository->findByCorrelationId($correlationId, $tenantId);

        $this->assertCount(1, $result);
    }

    public function testSearchWithAllFilters(): void
    {
        $tenantId = TenantId::generate();
        $from = new DateTimeImmutable('2026-01-01');
        $to = new DateTimeImmutable('2026-02-01');

        $rows = [$this->createRowData()];

        $queryBuilder = $this->createQueryBuilderMock($rows);
        $this->connection->method('createQueryBuilder')->willReturn($queryBuilder);

        $result = $this->repository->search(
            $tenantId,
            $from,
            $to,
            'ConnexionReussie',
            'User',
            100,
            0,
        );

        $this->assertCount(1, $result);
    }

    public function testEmptyResultReturnsEmptyArray(): void
    {
        $userId = Uuid::uuid4();
        $tenantId = TenantId::generate();

        $queryBuilder = $this->createQueryBuilderMock([]);
        $this->connection->method('createQueryBuilder')->willReturn($queryBuilder);

        $result = $this->repository->findByUser($userId, $tenantId);

        $this->assertSame([], $result);
    }

    /**
     * @return array<string, mixed>
     */
    private function createRowData(): array
    {
        return [
            'id' => Uuid::uuid4()->toString(),
            'aggregate_type' => 'User',
            'aggregate_id' => Uuid::uuid4()->toString(),
            'event_type' => 'ConnexionReussie',
            'payload' => '{"email_hash":"abc123"}',
            'metadata' => '{"tenant_id":"tenant-1","user_id":"user-1"}',
            'occurred_at' => '2026-02-03T10:30:00+00:00',
            'sequence_number' => '1',
        ];
    }

    /**
     * @param list<array<string, mixed>> $rows
     */
    private function createQueryBuilderMock(array $rows): QueryBuilder&MockObject
    {
        $queryBuilder = $this->createMock(QueryBuilder::class);
        $queryBuilder->method('select')->willReturnSelf();
        $queryBuilder->method('from')->willReturnSelf();
        $queryBuilder->method('where')->willReturnSelf();
        $queryBuilder->method('andWhere')->willReturnSelf();
        $queryBuilder->method('setParameter')->willReturnSelf();
        $queryBuilder->method('orderBy')->willReturnSelf();
        $queryBuilder->method('setMaxResults')->willReturnSelf();
        $queryBuilder->method('setFirstResult')->willReturnSelf();

        $result = $this->createMock(Result::class);
        $result->method('fetchAllAssociative')->willReturn($rows);
        $queryBuilder->method('executeQuery')->willReturn($result);

        return $queryBuilder;
    }
}
