<?php

declare(strict_types=1);

namespace App\Module\Absence\Infrastructure\Doctrine;

use App\Module\Absence\Domain\Entity\AbsenceRequest;
use App\Module\Absence\Domain\Enum\AbsenceStatus;
use App\Module\Absence\Domain\Enum\AbsenceType;
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
use App\Shared\Domain\Contract\UserInterface;
use DateTimeInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @extends ServiceEntityRepository<AbsenceRequest>
 */
class DoctrineAbsenceRequestRepository extends ServiceEntityRepository implements AbsenceRequestRepositoryInterface
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, AbsenceRequest::class);
    }

    public function findById(int $id): ?AbsenceRequest
    {
        return $this->find($id);
    }

    /**
     * Whether the user already has a PENDING or APPROVED absence that overlaps
     * the given date range. Two ranges overlap when start_a <= end_b and
     * end_a >= start_b.
     */
    public function hasOverlap(
        UserInterface $user,
        DateTimeInterface $startDate,
        DateTimeInterface $endDate,
        ?int $excludeId = null,
    ): bool {
        $qb = $this->createQueryBuilder('a')
            ->select('COUNT(a.id)')
            ->andWhere('a.user = :user')
            ->andWhere('a.status IN (:statuses)')
            ->andWhere('a.startDate <= :endDate')
            ->andWhere('a.endDate >= :startDate')
            ->setParameter('user', $user)
            ->setParameter('statuses', [AbsenceStatus::Pending, AbsenceStatus::Approved])
            ->setParameter('startDate', $startDate->format('Y-m-d'))
            ->setParameter('endDate', $endDate->format('Y-m-d'))
        ;

        if (null !== $excludeId) {
            $qb->andWhere('a.id != :excludeId')->setParameter('excludeId', $excludeId);
        }

        return (int) $qb->getQuery()->getSingleScalarResult() > 0;
    }

    /**
     * Absences (approved or pending) overlapping a date range, all employees —
     * used by the admin calendar view.
     *
     * @return AbsenceRequest[]
     */
    public function findInRange(DateTimeInterface $from, DateTimeInterface $to): array
    {
        return $this->createQueryBuilder('a')
            ->andWhere('a.status IN (:statuses)')
            ->andWhere('a.startDate <= :to')
            ->andWhere('a.endDate >= :from')
            ->setParameter('statuses', [AbsenceStatus::Pending, AbsenceStatus::Approved])
            ->setParameter('from', $from->format('Y-m-d'))
            ->setParameter('to', $to->format('Y-m-d'))
            ->orderBy('a.startDate', 'ASC')
            ->getQuery()
            ->getResult()
        ;
    }

    /**
     * @return AbsenceRequest[]
     */
    public function findFiltered(
        ?UserInterface $user = null,
        ?AbsenceStatus $status = null,
        ?AbsenceType $type = null,
        ?DateTimeInterface $from = null,
        ?DateTimeInterface $to = null,
    ): array {
        $qb = $this->createQueryBuilder('a')->orderBy('a.startDate', 'DESC');

        if (null !== $user) {
            $qb->andWhere('a.user = :user')->setParameter('user', $user);
        }
        if (null !== $status) {
            $qb->andWhere('a.status = :status')->setParameter('status', $status);
        }
        if (null !== $type) {
            $qb->andWhere('a.type = :type')->setParameter('type', $type);
        }
        if (null !== $from) {
            $qb->andWhere('a.endDate >= :from')->setParameter('from', $from->format('Y-m-d'));
        }
        if (null !== $to) {
            $qb->andWhere('a.startDate <= :to')->setParameter('to', $to->format('Y-m-d'));
        }

        return $qb->getQuery()->getResult();
    }
}
