<?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\Enum\ContractType;
use App\Enum\TrackingMode;
use App\Service\Contracts\EmployeeContractPhaseResolver;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;

/**
 * @internal
 */
final class EmployeeContractPhaseResolverTest extends TestCase
{
    public function testSinglePeriodYieldsSinglePhaseMarkedCurrent(): void
    {
        $employee = $this->buildEmployee([
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2020-06-01', 'end' => null],
        ]);

        $phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);

        self::assertCount(1, $phases);
        self::assertSame(ContractType::H39, $phases[0]->contractType);
        self::assertTrue($phases[0]->isCurrent);
        self::assertNull($phases[0]->endDate);
        self::assertSame(ContractNature::CDI, $phases[0]->contractNature);
    }

    public function testThreeConsecutivePeriodsSameSignatureCollapseIntoSinglePhase(): void
    {
        $employee = $this->buildEmployee([
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2020-06-01', 'end' => '2021-05-31'],
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2021-06-01', 'end' => '2022-05-31'],
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2022-06-01', 'end' => null],
        ]);

        $phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);

        self::assertCount(1, $phases);
        self::assertCount(3, $phases[0]->periodIds);
        self::assertSame('2020-06-01', $phases[0]->startDate->format('Y-m-d'));
        self::assertNull($phases[0]->endDate);
    }

    public function testSwitchFromH39ToForfaitProducesTwoPhasesMostRecentFirst(): void
    {
        $employee = $this->buildEmployee([
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2020-06-01', 'end' => '2026-04-30'],
            ['type' => ContractType::FORFAIT, 'hours' => 39, 'driver' => false, 'start' => '2026-05-01', 'end' => null],
        ]);

        $phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);

        self::assertCount(2, $phases);
        self::assertSame(ContractType::FORFAIT, $phases[0]->contractType);
        self::assertTrue($phases[0]->isCurrent);
        self::assertSame(ContractType::H39, $phases[1]->contractType);
        self::assertFalse($phases[1]->isCurrent);
        self::assertSame('2026-04-30', $phases[1]->endDate?->format('Y-m-d'));
    }

    public function testInterimBetweenTwoH39PeriodsBreaksThePhases(): void
    {
        $employee = $this->buildEmployee([
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2020-06-01', 'end' => '2023-12-31'],
            ['type' => ContractType::INTERIM, 'hours' => null, 'driver' => false, 'start' => '2024-01-01', 'end' => '2024-04-30'],
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2024-05-01', 'end' => null],
        ]);

        $phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);

        self::assertCount(3, $phases);
        self::assertSame(ContractType::H39, $phases[0]->contractType);
        self::assertSame(ContractType::INTERIM, $phases[1]->contractType);
        self::assertSame(ContractType::H39, $phases[2]->contractType);
    }

    public function testCustomPhasesSplitOnWeeklyHoursChange(): void
    {
        $employee = $this->buildEmployee([
            ['type' => ContractType::CUSTOM, 'hours' => 28, 'driver' => false, 'start' => '2024-01-01', 'end' => '2024-12-31'],
            ['type' => ContractType::CUSTOM, 'hours' => 30, 'driver' => false, 'start' => '2025-01-01', 'end' => null],
        ]);

        $phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);

        self::assertCount(2, $phases);
        self::assertSame(30, $phases[0]->weeklyHours);
        self::assertSame(28, $phases[1]->weeklyHours);
    }

    public function testPhasesSplitOnIsDriverChange(): void
    {
        $employee = $this->buildEmployee([
            ['type' => ContractType::H35, 'hours' => 35, 'driver' => false, 'start' => '2023-01-01', 'end' => '2024-12-31'],
            ['type' => ContractType::H35, 'hours' => 35, 'driver' => true, 'start' => '2025-01-01', 'end' => null],
        ]);

        $phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);

        self::assertCount(2, $phases);
        self::assertTrue($phases[0]->isDriver);
        self::assertFalse($phases[1]->isDriver);
    }

    public function testPhasesEntirelyBeforeDataStartDateAreFilteredOut(): void
    {
        // H35 phase ends before 2026-02-23 → must be hidden; H39 phase spans the date → kept.
        $employee = $this->buildEmployee([
            ['type' => ContractType::H35, 'hours' => 35, 'driver' => false, 'start' => '2014-07-01', 'end' => '2025-10-31'],
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2025-11-01', 'end' => null],
        ]);

        $phases = new EmployeeContractPhaseResolver('2026-02-23')->resolvePhases($employee);

        self::assertCount(1, $phases);
        self::assertSame(ContractType::H39, $phases[0]->contractType);
    }

    public function testPhaseEndingExactlyOnDataStartDateIsKept(): void
    {
        // Edge case: a phase whose endDate equals the data start date is kept
        // (the inequality is `>= $dataStart`, not strict).
        $employee = $this->buildEmployee([
            ['type' => ContractType::H35, 'hours' => 35, 'driver' => false, 'start' => '2020-01-01', 'end' => '2026-02-23'],
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2026-02-24', 'end' => null],
        ]);

        $phases = new EmployeeContractPhaseResolver('2026-02-23')->resolvePhases($employee);

        self::assertCount(2, $phases);
    }

    public function testNoFilteringWhenDataStartDateIsEmpty(): void
    {
        $employee = $this->buildEmployee([
            ['type' => ContractType::H35, 'hours' => 35, 'driver' => false, 'start' => '2014-07-01', 'end' => '2020-12-31'],
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2021-01-01', 'end' => null],
        ]);

        $phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);

        self::assertCount(2, $phases);
    }

    public function testInvalidDataStartDateStringIsTreatedAsNull(): void
    {
        $employee = $this->buildEmployee([
            ['type' => ContractType::H35, 'hours' => 35, 'driver' => false, 'start' => '2014-07-01', 'end' => '2020-12-31'],
            ['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2021-01-01', 'end' => null],
        ]);

        $phases = new EmployeeContractPhaseResolver('not-a-date')->resolvePhases($employee);

        self::assertCount(2, $phases);
    }

    /**
     * @param list<array{type: ContractType, hours: ?int, driver: bool, start: string, end: ?string}> $periodsSpec
     */
    private function buildEmployee(array $periodsSpec): Employee
    {
        $employee = new Employee();
        $id       = 0;
        foreach ($periodsSpec as $spec) {
            $contract = new Contract();
            $contract->setName($spec['type']->value);
            $contract->setTrackingMode(
                ContractType::FORFAIT === $spec['type'] ? TrackingMode::PRESENCE->value : TrackingMode::TIME->value
            );
            $contract->setWeeklyHours($spec['hours']);

            $period     = new EmployeeContractPeriod();
            $reflection = new ReflectionProperty(EmployeeContractPeriod::class, 'id');
            $reflection->setValue($period, ++$id);
            $period->setEmployee($employee);
            $period->setContract($contract);
            $period->setStartDate(new DateTimeImmutable($spec['start']));
            $period->setEndDate(null !== $spec['end'] ? new DateTimeImmutable($spec['end']) : null);
            $period->setContractNature(ContractNature::CDI);
            $period->setIsDriver($spec['driver']);
            $employee->getContractPeriods()->add($period);
        }

        return $employee;
    }
}
