<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Audit;

use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Ramsey\Uuid\UuidInterface;

/**
 * Repository for querying audit logs.
 *
 * Provides investigation queries for administrators and DPOs.
 * All queries are tenant-scoped for security.
 *
 * @see Story 1.7 - T9: Requetes d'investigation
 */
final readonly class AuditLogRepository
{
    public function __construct(
        private Connection $connection,
    ) {
    }

    /**
     * Get audit logs for a specific user.
     *
     * @see T9.1: Query GetAuditLogByUser
     *
     * @return list<AuditLogEntry>
     */
    public function findByUser(
        UuidInterface $userId,
        TenantId $tenantId,
        ?DateTimeImmutable $from = null,
        ?DateTimeImmutable $to = null,
        ?string $eventType = null,
        int $limit = 100,
        int $offset = 0,
    ): array {
        $qb = $this->connection->createQueryBuilder()
            ->select('*')
            ->from('audit_log')
            ->where("metadata->>'user_id' = :user_id")
            ->andWhere("metadata->>'tenant_id' = :tenant_id")
            ->setParameter('user_id', $userId->toString())
            ->setParameter('tenant_id', (string) $tenantId)
            ->orderBy('occurred_at', 'DESC')
            ->setMaxResults($limit)
            ->setFirstResult($offset);

        $this->applyFilters($qb, $from, $to, $eventType);

        return $this->mapResults($qb->executeQuery()->fetchAllAssociative());
    }

    /**
     * Get audit logs for a specific resource (aggregate).
     *
     * @see T9.2: Query GetAuditLogByResource
     *
     * @return list<AuditLogEntry>
     */
    public function findByResource(
        string $aggregateType,
        UuidInterface $aggregateId,
        TenantId $tenantId,
        ?DateTimeImmutable $from = null,
        ?DateTimeImmutable $to = null,
        int $limit = 100,
        int $offset = 0,
    ): array {
        $qb = $this->connection->createQueryBuilder()
            ->select('*')
            ->from('audit_log')
            ->where('aggregate_type = :aggregate_type')
            ->andWhere('aggregate_id = :aggregate_id')
            ->andWhere("metadata->>'tenant_id' = :tenant_id")
            ->setParameter('aggregate_type', $aggregateType)
            ->setParameter('aggregate_id', $aggregateId->toString())
            ->setParameter('tenant_id', (string) $tenantId)
            ->orderBy('occurred_at', 'DESC')
            ->setMaxResults($limit)
            ->setFirstResult($offset);

        $this->applyFilters($qb, $from, $to, null);

        return $this->mapResults($qb->executeQuery()->fetchAllAssociative());
    }

    /**
     * Get audit logs by correlation ID (trace a full request).
     *
     * @see T9.3: Query GetAuditLogByCorrelationId
     *
     * @return list<AuditLogEntry>
     */
    public function findByCorrelationId(
        string $correlationId,
        TenantId $tenantId,
    ): array {
        $qb = $this->connection->createQueryBuilder()
            ->select('*')
            ->from('audit_log')
            ->where("metadata->>'correlation_id' = :correlation_id")
            ->andWhere("metadata->>'tenant_id' = :tenant_id")
            ->setParameter('correlation_id', $correlationId)
            ->setParameter('tenant_id', (string) $tenantId)
            ->orderBy('occurred_at', 'ASC');

        return $this->mapResults($qb->executeQuery()->fetchAllAssociative());
    }

    /**
     * Search audit logs with multiple filters.
     *
     * @see T9.4: Filtres avances
     *
     * @return list<AuditLogEntry>
     */
    public function search(
        TenantId $tenantId,
        ?DateTimeImmutable $from = null,
        ?DateTimeImmutable $to = null,
        ?string $eventType = null,
        ?string $aggregateType = null,
        int $limit = 100,
        int $offset = 0,
    ): array {
        $qb = $this->connection->createQueryBuilder()
            ->select('*')
            ->from('audit_log')
            ->where("metadata->>'tenant_id' = :tenant_id")
            ->setParameter('tenant_id', (string) $tenantId)
            ->orderBy('occurred_at', 'DESC')
            ->setMaxResults($limit)
            ->setFirstResult($offset);

        $this->applyFilters($qb, $from, $to, $eventType);

        if ($aggregateType !== null) {
            $qb->andWhere('aggregate_type = :aggregate_type')
                ->setParameter('aggregate_type', $aggregateType);
        }

        return $this->mapResults($qb->executeQuery()->fetchAllAssociative());
    }

    /**
     * @param \Doctrine\DBAL\Query\QueryBuilder $qb
     */
    private function applyFilters(
        $qb,
        ?DateTimeImmutable $from,
        ?DateTimeImmutable $to,
        ?string $eventType,
    ): void {
        if ($from !== null) {
            $qb->andWhere('occurred_at >= :from')
                ->setParameter('from', $from->format('Y-m-d H:i:s.uP'));
        }

        if ($to !== null) {
            $qb->andWhere('occurred_at <= :to')
                ->setParameter('to', $to->format('Y-m-d H:i:s.uP'));
        }

        if ($eventType !== null) {
            $qb->andWhere('event_type = :event_type')
                ->setParameter('event_type', $eventType);
        }
    }

    /**
     * @param list<array<string, mixed>> $rows
     *
     * @return list<AuditLogEntry>
     */
    private function mapResults(array $rows): array
    {
        return array_map(
            static fn (array $row): AuditLogEntry => AuditLogEntry::fromDatabaseRow($row),
            $rows,
        );
    }
}
