<?php

declare(strict_types=1);

namespace App\Tests\Service\WorkHours;

use App\Entity\Absence;
use App\Entity\AbsenceType;
use App\Entity\Contract;
use App\Entity\Employee;
use App\Service\Contracts\EmployeeContractResolver;
use App\Service\WorkHours\DailyReferenceMinutesResolver;
use App\Service\WorkHours\WorkedHoursCreditPolicy;
use DateTime;
use PHPUnit\Framework\TestCase;

/**
 * @internal
 */
final class WorkedHoursCreditPolicyTest extends TestCase
{
    public function testComputeCreditedMinutesFor35hHalfDay(): void
    {
        $policy  = new WorkedHoursCreditPolicy($this->buildResolverStub(), new DailyReferenceMinutesResolver());
        $absence = $this->buildAbsence(trackMode: Contract::TRACKING_TIME, weeklyHours: 35, countAsWorked: true);

        $minutes = $policy->computeCreditedMinutes($absence, '2026-02-16', true, false);

        self::assertSame(210, $minutes);
    }

    public function testComputeCreditedMinutesFor4hContractUsesWorkDaysSchedule(): void
    {
        // 4h contract with schedule Mon 2h + Thu 2h
        $schedule = [1 => 120, 4 => 120];
        $policy   = new WorkedHoursCreditPolicy($this->buildResolverStub($schedule), new DailyReferenceMinutesResolver());
        $absence  = $this->buildAbsence(trackMode: Contract::TRACKING_TIME, weeklyHours: 4, countAsWorked: true);

        // 2026-02-16 is a Monday: full day absence credits 2h (matches scheduled Monday)
        self::assertSame(120, $policy->computeCreditedMinutes($absence, '2026-02-16', true, true));
    }

    public function testComputeCreditedMinutesFor4hContractOnUnscheduledDayReturnsZero(): void
    {
        // 4h contract with schedule Mon 2h + Thu 2h
        $schedule = [1 => 120, 4 => 120];
        $policy   = new WorkedHoursCreditPolicy($this->buildResolverStub($schedule), new DailyReferenceMinutesResolver());
        $absence  = $this->buildAbsence(
            trackMode: Contract::TRACKING_TIME,
            weeklyHours: 4,
            countAsWorked: true,
            start: '2026-02-17',
            end: '2026-02-17',
        );

        // 2026-02-17 is a Tuesday — not a scheduled workday → 0 credit
        self::assertSame(0, $policy->computeCreditedMinutes($absence, '2026-02-17', true, true));
    }

    public function testComputeCreditedMinutesHalfDayOnAsymmetricScheduleDay(): void
    {
        // Asymmetric schedule: Monday is a 3h day (180 min)
        $schedule = [1 => 180];
        $policy   = new WorkedHoursCreditPolicy($this->buildResolverStub($schedule), new DailyReferenceMinutesResolver());
        $absence  = $this->buildAbsence(trackMode: Contract::TRACKING_TIME, weeklyHours: 3, countAsWorked: true);

        // 2026-02-16 Monday, morning only → round(180/2 * 1) = 90 min
        self::assertSame(90, $policy->computeCreditedMinutes($absence, '2026-02-16', true, false));
        // Afternoon only → same 90 min (half of day)
        self::assertSame(90, $policy->computeCreditedMinutes($absence, '2026-02-16', false, true));
        // Full day → 180 min
        self::assertSame(180, $policy->computeCreditedMinutes($absence, '2026-02-16', true, true));
    }

    public function testComputeCreditedPresenceUnitsForPresenceContract(): void
    {
        $policy  = new WorkedHoursCreditPolicy($this->buildResolverStub(), new DailyReferenceMinutesResolver());
        $absence = $this->buildAbsence(trackMode: Contract::TRACKING_PRESENCE, weeklyHours: null, countAsWorked: true);

        // Forfait : les absences ne créditent jamais de présence, seules les checkboxes comptent.
        self::assertSame(0.0, $policy->computeCreditedPresenceUnits($absence, '2026-02-16', true, false));
        self::assertSame(0.0, $policy->computeCreditedPresenceUnits($absence, '2026-02-16', true, true));
    }

    public function testNoCreditWhenAbsenceTypeDoesNotCount(): void
    {
        $policy  = new WorkedHoursCreditPolicy($this->buildResolverStub(), new DailyReferenceMinutesResolver());
        $absence = $this->buildAbsence(trackMode: Contract::TRACKING_TIME, weeklyHours: 35, countAsWorked: false);

        self::assertSame(0, $policy->computeCreditedMinutes($absence, '2026-02-16', true, true));
        self::assertSame(0.0, $policy->computeCreditedPresenceUnits($absence, '2026-02-16', true, true));
    }

    private function buildAbsence(
        string $trackMode,
        ?int $weeklyHours,
        bool $countAsWorked,
        string $start = '2026-02-16',
        string $end = '2026-02-16',
    ): Absence {
        $contract = new Contract()
            ->setName('Contrat test')
            ->setTrackingMode($trackMode)
            ->setWeeklyHours($weeklyHours)
        ;
        $employee = new Employee()
            ->setFirstName('Alice')
            ->setLastName('Durand')
            ->setContract($contract)
        ;
        $type = new AbsenceType()
            ->setCode('CP')
            ->setLabel('Congés')
            ->setColor('#000')
            ->setCountAsWorkedHours($countAsWorked)
        ;

        return new Absence()
            ->setEmployee($employee)
            ->setType($type)
            ->setStartDate(new DateTime($start))
            ->setEndDate(new DateTime($end))
        ;
    }

    /**
     * @param null|array<int, int> $schedule
     */
    private function buildResolverStub(?array $schedule = null): EmployeeContractResolver
    {
        $resolver = $this->createStub(EmployeeContractResolver::class);
        $resolver
            ->method('resolveForEmployeeAndDate')
            ->willReturnCallback(static fn (Employee $employee): ?Contract => $employee->getContract())
        ;
        $resolver
            ->method('resolveWorkDaysMinutesForEmployeeAndDate')
            ->willReturn($schedule)
        ;

        return $resolver;
    }
}
