<?php

declare(strict_types=1);

namespace App\Service\Contracts;

use App\Entity\Contract;
use App\Entity\EmployeeContractPeriod;
use App\Enum\ContractNature;
use App\Enum\TrackingMode;
use DateTimeImmutable;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

final class EmployeeContractPeriodValidator
{
    public function assertPeriodDates(
        DateTimeImmutable $startDate,
        ?DateTimeImmutable $endDate,
        ContractNature $nature,
        bool $allowCdiEndDate = false
    ): void {
        if (null !== $endDate && $endDate < $startDate) {
            throw new UnprocessableEntityHttpException('contractEndDate cannot be before contractStartDate.');
        }

        if ($nature->requiresEndDate() && null === $endDate) {
            throw new UnprocessableEntityHttpException('contractEndDate is required for CDD and INTERIM.');
        }

        if (!$allowCdiEndDate && ContractNature::CDI === $nature && null !== $endDate) {
            throw new UnprocessableEntityHttpException('contractEndDate must be empty for CDI.');
        }
    }

    public function assertCloseEndDateCanBeApplied(
        DateTimeImmutable $startDate,
        ?DateTimeImmutable $currentEndDate,
        DateTimeImmutable $requestedEndDate,
        ContractNature $nature
    ): void {
        $this->assertPeriodDates($startDate, $requestedEndDate, $nature, true);

        if (null !== $currentEndDate && $requestedEndDate > $currentEndDate) {
            throw new UnprocessableEntityHttpException('contractEndDate cannot be increased on current contract.');
        }
    }

    public function assertNextStartDateCompatible(
        DateTimeImmutable $startDate,
        EmployeeContractPeriod $currentPeriod
    ): void {
        $currentEndDate = $currentPeriod->getEndDate();
        if (null === $currentEndDate) {
            if ($startDate <= $currentPeriod->getStartDate()) {
                throw new UnprocessableEntityHttpException('contractStartDate must be after current contract start date.');
            }

            return;
        }

        if ($startDate <= $currentEndDate) {
            throw new UnprocessableEntityHttpException('contractStartDate must be after current contract end date.');
        }
    }

    /**
     * Validates the per-period work schedule (`workDaysHours`) against the contract.
     *
     * Mandatory for non-standard TIME contracts (weeklyHours ∉ {35, 39}, non-INTERIM,
     * non-Forfait). Forbidden on standard/forfait/interim contracts (ambiguity).
     * When provided, sum of minutes MUST equal weeklyHours × 60.
     *
     * @param null|array<int, int> $workDaysHours
     */
    public function assertWorkDaysHours(?Contract $contract, ContractNature $nature, ?array $workDaysHours): void
    {
        if (null === $contract) {
            return;
        }

        $trackingMode = $contract->getTrackingMode();
        $weeklyHours  = $contract->getWeeklyHours();
        $isStandard   = 35 === $weeklyHours || 39 === $weeklyHours;
        $isForfait    = TrackingMode::PRESENCE->value === $trackingMode;
        $isInterim    = ContractNature::INTERIM === $nature;

        if ($isForfait || $isInterim || $isStandard) {
            if (null !== $workDaysHours && [] !== $workDaysHours) {
                throw new UnprocessableEntityHttpException('workDaysHours must not be provided for Forfait, Interim or 35h/39h contracts.');
            }

            return;
        }

        if (null === $workDaysHours || [] === $workDaysHours) {
            throw new UnprocessableEntityHttpException('workDaysHours is required for non-standard contracts.');
        }

        $totalMinutes = 0;
        foreach ($workDaysHours as $isoDay => $minutes) {
            if (!is_int($isoDay) && !(is_string($isoDay) && ctype_digit($isoDay))) {
                throw new UnprocessableEntityHttpException('workDaysHours keys must be iso weekdays 1-5 (Mon-Fri) as integers.');
            }
            $iso = (int) $isoDay;
            if ($iso < 1 || $iso > 5) {
                throw new UnprocessableEntityHttpException('workDaysHours keys must be iso weekdays 1-5 (Mon-Fri).');
            }

            if (!is_int($minutes) || $minutes < 0) {
                throw new UnprocessableEntityHttpException('workDaysHours values must be non-negative integer minutes.');
            }
            $totalMinutes += $minutes;
        }

        $expectedMinutes = ($weeklyHours ?? 0) * 60;
        if ($totalMinutes !== $expectedMinutes) {
            throw new UnprocessableEntityHttpException(sprintf(
                'workDaysHours total must equal contract weekly hours: got %d min, expected %d min.',
                $totalMinutes,
                $expectedMinutes
            ));
        }
    }
}
