<?php

declare(strict_types=1);

namespace App\Service\Contracts;

use App\Entity\Contract;
use App\Entity\Employee;
use App\Entity\EmployeeContractPeriod;
use App\Entity\InterimAgency;
use App\Enum\ContractNature;
use App\Repository\EmployeeContractPeriodRepository;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

final readonly class EmployeeContractPeriodManager implements EmployeeContractPeriodManagerInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private EmployeeContractPeriodRepository $periodRepository,
        private EmployeeContractPeriodBuilder $periodBuilder,
        private EmployeeContractPeriodValidator $periodValidator,
    ) {}

    public function ensureContractPeriodExists(
        Employee $employee,
        Contract $contract,
        DateTimeImmutable $startDate,
        ?DateTimeImmutable $endDate,
        ContractNature $nature,
        bool $isDriver = false,
        ?array $workDaysHours = null,
        ?int $interimAgencyId = null,
    ): void {
        $this->periodValidator->assertPeriodDates($startDate, $endDate, $nature);
        $this->periodValidator->assertWorkDaysHours($contract, $nature, $workDaysHours);

        $covered = $this->periodRepository->findOneCoveringDate($employee, $startDate);
        if (null !== $covered) {
            return;
        }

        $interimAgency = $this->resolveInterimAgency($interimAgencyId);
        $this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature, $isDriver, $workDaysHours, $interimAgency);
        $this->entityManager->flush();
    }

    public function closeCurrentPeriod(
        ?EmployeeContractPeriod $todayPeriod,
        DateTimeImmutable $requestedEndDate,
        bool $paidLeaveSettled,
        ?string $comment = null,
        bool $isAlreadyEnded = false
    ): void {
        if (null === $todayPeriod) {
            throw new UnprocessableEntityHttpException('No active contract period to close.');
        }

        if (!$isAlreadyEnded) {
            $this->periodValidator->assertCloseEndDateCanBeApplied(
                $todayPeriod->getStartDate(),
                $todayPeriod->getEndDate(),
                $requestedEndDate,
                $todayPeriod->getContractNatureEnum()
            );
        }

        $todayPeriod->setEndDate($requestedEndDate);
        $todayPeriod->setPaidLeaveSettled($paidLeaveSettled);
        $todayPeriod->setComment($comment);
        $this->entityManager->flush();
    }

    public function createNextPeriod(
        Employee $employee,
        Contract $contract,
        DateTimeImmutable $startDate,
        ?DateTimeImmutable $endDate,
        ContractNature $nature,
        ?EmployeeContractPeriod $todayPeriod,
        bool $isDriver = false,
        ?array $workDaysHours = null,
        ?int $interimAgencyId = null,
    ): void {
        $this->periodValidator->assertPeriodDates($startDate, $endDate, $nature);
        $this->periodValidator->assertWorkDaysHours($contract, $nature, $workDaysHours);

        if (null !== $todayPeriod) {
            $this->periodValidator->assertNextStartDateCompatible($startDate, $todayPeriod);

            if (null === $todayPeriod->getEndDate()) {
                $todayPeriod->setEndDate($startDate->modify('-1 day'));
            }
        }

        $interimAgency = $this->resolveInterimAgency($interimAgencyId);
        $this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature, $isDriver, $workDaysHours, $interimAgency);
        $this->entityManager->flush();
    }

    /**
     * @param null|array<int, int> $workDaysHours
     */
    private function persistNewPeriod(
        Employee $employee,
        Contract $contract,
        DateTimeImmutable $startDate,
        ?DateTimeImmutable $endDate,
        ContractNature $nature,
        bool $isDriver = false,
        ?array $workDaysHours = null,
        ?InterimAgency $interimAgency = null,
    ): void {
        $period = $this->periodBuilder->build($employee, $contract, $startDate, $endDate, $nature, $isDriver, $workDaysHours, $interimAgency);
        $this->entityManager->persist($period);
    }

    private function resolveInterimAgency(?int $id): ?InterimAgency
    {
        if (null === $id) {
            return null;
        }

        $agency = $this->entityManager->find(InterimAgency::class, $id);
        if (null === $agency) {
            throw new UnprocessableEntityHttpException(sprintf('Interim agency with id %d not found.', $id));
        }

        return $agency;
    }
}
