<?php

declare(strict_types=1);

namespace App\Repository;

use App\Entity\Employee;
use App\Entity\EmployeeContractPeriod;
use App\Repository\Contract\EmployeeContractPeriodReadRepositoryInterface;
use DateTimeImmutable;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @extends ServiceEntityRepository<EmployeeContractPeriod>
 */
final class EmployeeContractPeriodRepository extends ServiceEntityRepository implements EmployeeContractPeriodReadRepositoryInterface
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, EmployeeContractPeriod::class);
    }

    /**
     * @param list<Employee> $employees
     *
     * @return list<EmployeeContractPeriod>
     */
    public function findByEmployeesAndDateRange(array $employees, DateTimeImmutable $from, DateTimeImmutable $to): array
    {
        if ([] === $employees) {
            return [];
        }

        return $this->createQueryBuilder('p')
            ->andWhere('p.employee IN (:employees)')
            ->andWhere('p.startDate <= :to')
            ->andWhere('p.endDate IS NULL OR p.endDate >= :from')
            ->setParameter('employees', $employees)
            ->setParameter('from', $from)
            ->setParameter('to', $to)
            ->orderBy('p.startDate', 'ASC')
            ->getQuery()
            ->getResult()
        ;
    }

    public function findOneCoveringDate(Employee $employee, DateTimeImmutable $date): ?EmployeeContractPeriod
    {
        return $this->createQueryBuilder('p')
            ->andWhere('p.employee = :employee')
            ->andWhere('p.startDate <= :date')
            ->andWhere('p.endDate IS NULL OR p.endDate >= :date')
            ->setParameter('employee', $employee)
            ->setParameter('date', $date)
            ->orderBy('p.startDate', 'DESC')
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult()
        ;
    }

    public function findLatestPeriod(Employee $employee): ?EmployeeContractPeriod
    {
        return $this->createQueryBuilder('p')
            ->andWhere('p.employee = :employee')
            ->setParameter('employee', $employee)
            ->orderBy('p.startDate', 'DESC')
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult()
        ;
    }

    /**
     * Latest contract period (max startDate) for every employee that has at least one.
     *
     * @return EmployeeContractPeriod[]
     */
    public function findLatestPeriodsForAllEmployees(): array
    {
        return $this->createQueryBuilder('p')
            ->andWhere('p.startDate = (
                SELECT MAX(p2.startDate)
                FROM App\Entity\EmployeeContractPeriod p2
                WHERE p2.employee = p.employee
            )')
            ->getQuery()
            ->getResult()
        ;
    }

    public function closeOpenPeriods(Employee $employee, DateTimeImmutable $endDate): int
    {
        return $this->createQueryBuilder('p')
            ->update()
            ->set('p.endDate', ':endDate')
            ->andWhere('p.employee = :employee')
            ->andWhere('p.endDate IS NULL')
            ->setParameter('employee', $employee)
            ->setParameter('endDate', $endDate)
            ->getQuery()
            ->execute()
        ;
    }

    public function hasPaidLeaveSettledClosureBetween(
        Employee $employee,
        DateTimeImmutable $from,
        DateTimeImmutable $to
    ): bool {
        $count = $this->createQueryBuilder('p')
            ->select('COUNT(p.id)')
            ->andWhere('p.employee = :employee')
            ->andWhere('p.paidLeaveSettled = :paidLeaveSettled')
            ->andWhere('p.endDate IS NOT NULL')
            ->andWhere('p.endDate >= :from')
            ->andWhere('p.endDate <= :to')
            ->setParameter('employee', $employee)
            ->setParameter('paidLeaveSettled', true)
            ->setParameter('from', $from)
            ->setParameter('to', $to)
            ->getQuery()
            ->getSingleScalarResult()
        ;

        return (int) $count > 0;
    }

    public function findLatestPaidLeaveSettledClosureDateBetween(
        Employee $employee,
        DateTimeImmutable $from,
        DateTimeImmutable $to
    ): ?DateTimeImmutable {
        $result = $this->createQueryBuilder('p')
            ->select('p.endDate AS endDate')
            ->andWhere('p.employee = :employee')
            ->andWhere('p.paidLeaveSettled = :paidLeaveSettled')
            ->andWhere('p.endDate IS NOT NULL')
            ->andWhere('p.endDate >= :from')
            ->andWhere('p.endDate <= :to')
            ->setParameter('employee', $employee)
            ->setParameter('paidLeaveSettled', true)
            ->setParameter('from', $from)
            ->setParameter('to', $to)
            ->orderBy('p.endDate', 'DESC')
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult()
        ;

        if (!is_array($result) || !isset($result['endDate']) || !$result['endDate'] instanceof DateTimeImmutable) {
            return null;
        }

        return $result['endDate'];
    }
}
