<?php

declare(strict_types=1);

namespace App\Tests\Service\Contracts;

use App\Entity\Contract;
use App\Entity\Employee;
use App\Entity\EmployeeContractPeriod;
use App\Enum\ContractNature;
use App\Service\Contracts\EmployeeContractPeriodValidator;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

/**
 * @internal
 */
final class EmployeeContractPeriodValidatorTest extends TestCase
{
    private EmployeeContractPeriodValidator $validator;

    protected function setUp(): void
    {
        $this->validator = new EmployeeContractPeriodValidator();
    }

    public function testAssertPeriodDatesRejectsEndBeforeStart(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('contractEndDate cannot be before contractStartDate.');

        $this->validator->assertPeriodDates(
            new DateTimeImmutable('2026-03-10'),
            new DateTimeImmutable('2026-03-01'),
            ContractNature::CDD
        );
    }

    public function testAssertPeriodDatesRejectsMissingEndDateForCdd(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('contractEndDate is required for CDD and INTERIM.');

        $this->validator->assertPeriodDates(
            new DateTimeImmutable('2026-03-01'),
            null,
            ContractNature::CDD
        );
    }

    public function testAssertPeriodDatesRejectsEndDateForCdiWhenNotAllowed(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('contractEndDate must be empty for CDI.');

        $this->validator->assertPeriodDates(
            new DateTimeImmutable('2026-03-01'),
            new DateTimeImmutable('2026-03-10'),
            ContractNature::CDI
        );
    }

    public function testAssertCloseEndDateCanBeAppliedRejectsIncrease(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('contractEndDate cannot be increased on current contract.');

        $this->validator->assertCloseEndDateCanBeApplied(
            new DateTimeImmutable('2026-03-01'),
            new DateTimeImmutable('2026-03-10'),
            new DateTimeImmutable('2026-03-11'),
            ContractNature::CDI
        );
    }

    public function testAssertNextStartDateCompatibleRejectsWhenNotAfterCurrentOpenStart(): void
    {
        $currentPeriod = $this->buildCurrentPeriod('2026-03-05', null);

        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('contractStartDate must be after current contract start date.');

        $this->validator->assertNextStartDateCompatible(new DateTimeImmutable('2026-03-05'), $currentPeriod);
    }

    public function testAssertNextStartDateCompatibleRejectsWhenNotAfterCurrentClosedEnd(): void
    {
        $currentPeriod = $this->buildCurrentPeriod('2026-03-01', '2026-03-10');

        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('contractStartDate must be after current contract end date.');

        $this->validator->assertNextStartDateCompatible(new DateTimeImmutable('2026-03-10'), $currentPeriod);
    }

    public function testAssertWorkDaysHoursAcceptsNullForStandardContract(): void
    {
        $this->validator->assertWorkDaysHours($this->buildContract(35), ContractNature::CDI, null);
        self::assertTrue(true); // no exception
    }

    public function testAssertWorkDaysHoursRejectsScheduleOn35hContract(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->validator->assertWorkDaysHours($this->buildContract(35), ContractNature::CDI, [1 => 120]);
    }

    public function testAssertWorkDaysHoursRejectsScheduleOnForfaitContract(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->validator->assertWorkDaysHours($this->buildContract(null, Contract::TRACKING_PRESENCE), ContractNature::CDI, [1 => 120]);
    }

    public function testAssertWorkDaysHoursAcceptsNullForInterim(): void
    {
        $this->validator->assertWorkDaysHours($this->buildContract(4), ContractNature::INTERIM, null);
        self::assertTrue(true);
    }

    public function testAssertWorkDaysHoursRequiresScheduleForCustomContract(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('workDaysHours is required');
        $this->validator->assertWorkDaysHours($this->buildContract(4), ContractNature::CDI, null);
    }

    public function testAssertWorkDaysHoursRequiresScheduleForCustomContractOnEmptyArray(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('workDaysHours is required');
        $this->validator->assertWorkDaysHours($this->buildContract(4), ContractNature::CDI, []);
    }

    public function testAssertWorkDaysHoursRejectsIsoOutsideOneToFive(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('iso weekdays 1-5');
        $this->validator->assertWorkDaysHours($this->buildContract(4), ContractNature::CDI, [6 => 120, 7 => 120]);
    }

    public function testAssertWorkDaysHoursRejectsIsoZero(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('iso weekdays 1-5');
        $this->validator->assertWorkDaysHours($this->buildContract(4), ContractNature::CDI, [0 => 240]);
    }

    public function testAssertWorkDaysHoursRejectsNegativeMinutes(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('non-negative integer minutes');
        $this->validator->assertWorkDaysHours($this->buildContract(4), ContractNature::CDI, [1 => -120, 4 => 360]);
    }

    public function testAssertWorkDaysHoursRejectsSumMismatch(): void
    {
        $this->expectException(UnprocessableEntityHttpException::class);
        $this->expectExceptionMessage('total must equal contract weekly hours');
        $this->validator->assertWorkDaysHours($this->buildContract(4), ContractNature::CDI, [1 => 60, 4 => 60]);
    }

    public function testAssertWorkDaysHoursAcceptsValidScheduleFor4hContract(): void
    {
        $this->validator->assertWorkDaysHours($this->buildContract(4), ContractNature::CDI, [1 => 120, 4 => 120]);
        self::assertTrue(true);
    }

    private function buildContract(?int $weeklyHours, string $trackingMode = Contract::TRACKING_TIME): Contract
    {
        return new Contract()
            ->setName('Test')
            ->setTrackingMode($trackingMode)
            ->setWeeklyHours($weeklyHours)
        ;
    }

    private function buildCurrentPeriod(string $startDate, ?string $endDate): EmployeeContractPeriod
    {
        $contract = new Contract()
            ->setName('35h')
            ->setTrackingMode(Contract::TRACKING_TIME)
            ->setWeeklyHours(35)
        ;
        $employee = new Employee()
            ->setFirstName('Test')
            ->setLastName('User')
            ->setContract($contract)
        ;

        return new EmployeeContractPeriod()
            ->setEmployee($employee)
            ->setContract($contract)
            ->setStartDate(new DateTimeImmutable($startDate))
            ->setEndDate(null !== $endDate ? new DateTimeImmutable($endDate) : null)
            ->setContractNature(ContractNature::CDI)
        ;
    }
}
