<?php

declare(strict_types=1);

namespace App\Tests\Service\WorkHours;

use App\Entity\Contract;
use App\Entity\Employee;
use App\Entity\EmployeeContractPeriod;
use App\Enum\TrackingMode;
use App\Service\WorkHours\StructuralOvertimeContingentCalculator;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;

/**
 * @internal
 */
final class StructuralOvertimeContingentCalculatorTest extends TestCase
{
    public function testFullYear39hCreditsConstantMonthlyBase(): void
    {
        $calc     = new StructuralOvertimeContingentCalculator();
        $employee = $this->employeeWithPeriod(39, '2020-01-01', null);

        $months = $calc->monthlyStructuralMinutes($employee, 2026);

        // (39 - 35) x 260 = 1040 minutes (17,33 h) chaque mois plein.
        self::assertSame(1040, $months[1]);
        self::assertSame(1040, $months[6]);
        self::assertSame(1040, $months[12]);
        self::assertSame(12 * 1040, $calc->totalStructuralMinutes($employee, 2026));
    }

    public function testCustomAbove35hUsesGeneralizedFormula(): void
    {
        $calc     = new StructuralOvertimeContingentCalculator();
        $employee = $this->employeeWithPeriod(40, '2020-01-01', null);

        // (40 - 35) x 260 = 1300 minutes par mois.
        self::assertSame(1300, $calc->monthlyStructuralMinutes($employee, 2026)[1]);
    }

    public function test35hAndBelowCreditNothing(): void
    {
        $calc = new StructuralOvertimeContingentCalculator();

        self::assertSame(0, $calc->totalStructuralMinutes($this->employeeWithPeriod(35, '2020-01-01', null), 2026));
        self::assertSame(0, $calc->totalStructuralMinutes($this->employeeWithPeriod(28, '2020-01-01', null), 2026));
    }

    public function testMidMonthEntryIsProratedByContractedDays(): void
    {
        $calc = new StructuralOvertimeContingentCalculator();
        // Embauche le 16 janvier 2026 : 16 jours contractés sur 31.
        $employee = $this->employeeWithPeriod(39, '2026-01-16', null);

        $months = $calc->monthlyStructuralMinutes($employee, 2026);

        self::assertSame((int) round(1040 * 16 / 31), $months[1]);
        self::assertSame(1040, $months[2]);
    }

    public function testMonthsOutsidePeriodCreditNothing(): void
    {
        $calc = new StructuralOvertimeContingentCalculator();
        // Contrat clos fin mars 2026.
        $employee = $this->employeeWithPeriod(39, '2020-01-01', '2026-03-31');

        $months = $calc->monthlyStructuralMinutes($employee, 2026);

        self::assertSame(1040, $months[3]);
        self::assertSame(0, $months[4]);
        self::assertSame(0, $months[12]);
    }

    public function testForfaitPeriodCreditsNothing(): void
    {
        $calc = new StructuralOvertimeContingentCalculator();

        $contract = new Contract()
            ->setName('Forfait')
            ->setTrackingMode(TrackingMode::PRESENCE)
            ->setWeeklyHours(null)
        ;
        $period = new EmployeeContractPeriod()
            ->setContract($contract)
            ->setStartDate(new DateTimeImmutable('2020-01-01'))
        ;
        $employee = new Employee();
        $employee->getContractPeriods()->add($period);

        self::assertSame(0, $calc->totalStructuralMinutes($employee, 2026));
    }

    public function testInterimAbove35hCreditsNothing(): void
    {
        $calc = new StructuralOvertimeContingentCalculator();

        $contract = new Contract()
            ->setName('Interim')
            ->setTrackingMode(TrackingMode::TIME)
            ->setWeeklyHours(39)
        ;
        $period = new EmployeeContractPeriod()
            ->setContract($contract)
            ->setStartDate(new DateTimeImmutable('2020-01-01'))
        ;
        $employee = new Employee();
        $employee->getContractPeriods()->add($period);

        self::assertSame(0, $calc->totalStructuralMinutes($employee, 2026));
    }

    private function employeeWithPeriod(int $weeklyHours, string $start, ?string $end): Employee
    {
        $contract = new Contract()
            ->setName('CDI')
            ->setTrackingMode(TrackingMode::TIME)
            ->setWeeklyHours($weeklyHours)
        ;
        $period = new EmployeeContractPeriod()
            ->setContract($contract)
            ->setStartDate(new DateTimeImmutable($start))
            ->setEndDate(null === $end ? null : new DateTimeImmutable($end))
        ;
        $employee = new Employee();
        $employee->getContractPeriods()->add($period);

        return $employee;
    }
}
