<?php

declare(strict_types=1);

namespace App\Tests\Service\WorkHours;

use App\Entity\Contract;
use App\Service\Contracts\EmployeeContractResolver;
use App\Service\PublicHolidayServiceInterface;
use App\Service\WorkHours\DailyReferenceMinutesResolver;
use App\Service\WorkHours\HolidayVirtualHoursResolver;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use RuntimeException;

/**
 * @internal
 */
final class HolidayVirtualHoursResolverTest extends TestCase
{
    private HolidayVirtualHoursResolver $resolver;

    protected function setUp(): void
    {
        $holidayService = $this->createStub(PublicHolidayServiceInterface::class);
        $holidayService->method('getHolidaysDayByYears')->willReturnCallback(
            static fn (string $zone, string $year): array => [
                // Mon 14/07/2025 (lundi)
                '2025-07-14' => '14 juillet',
                // Fri 15/08/2025 (vendredi)
                '2025-08-15' => '15 août',
                // Sat 11/11/2025 (samedi)
                '2025-11-15' => 'Samedi test',
                // Thu 25/12/2025
                '2025-12-25' => 'Noël',
            ]
        );

        $this->resolver = new HolidayVirtualHoursResolver(
            new DailyReferenceMinutesResolver(),
            $holidayService,
            $this->createStub(EmployeeContractResolver::class),
        );
    }

    public function testReturnsZeroWhenContractIsNull(): void
    {
        self::assertSame(0, $this->resolver->resolveVirtualCredit(null, new DateTimeImmutable('2025-07-14')));
    }

    public function testReturnsZeroForForfaitPresenceContract(): void
    {
        $contract = new Contract()
            ->setName('Forfait')
            ->setTrackingMode('PRESENCE')
            ->setWeeklyHours(null)
        ;

        self::assertSame(0, $this->resolver->resolveVirtualCredit($contract, new DateTimeImmutable('2025-07-14')));
    }

    public function testReturnsZeroWhenDayIsNotHoliday(): void
    {
        $contract = $this->build35hContract();
        self::assertSame(0, $this->resolver->resolveVirtualCredit($contract, new DateTimeImmutable('2025-07-07')));
    }

    public function testReturnsZeroWhenHolidayFallsOnSaturday(): void
    {
        $contract = $this->build35hContract();
        self::assertSame(0, $this->resolver->resolveVirtualCredit($contract, new DateTimeImmutable('2025-11-15')));
    }

    public function test35hMondayGetsSevenHours(): void
    {
        $contract = $this->build35hContract();
        self::assertSame(7 * 60, $this->resolver->resolveVirtualCredit($contract, new DateTimeImmutable('2025-07-14')));
    }

    public function test39hMondayGetsEightHours(): void
    {
        $contract = $this->build39hContract();
        self::assertSame(8 * 60, $this->resolver->resolveVirtualCredit($contract, new DateTimeImmutable('2025-07-14')));
    }

    public function test39hFridayGetsSevenHours(): void
    {
        $contract = $this->build39hContract();
        self::assertSame(7 * 60, $this->resolver->resolveVirtualCredit($contract, new DateTimeImmutable('2025-08-15')));
    }

    public function testCustomContractUsesProRataReference(): void
    {
        $contract = new Contract()
            ->setName('28h')
            ->setTrackingMode('TIME')
            ->setWeeklyHours(28)
        ;
        // 28h / 5 = 5.6h = 336 min
        self::assertSame(336, $this->resolver->resolveVirtualCredit($contract, new DateTimeImmutable('2025-07-14')));
    }

    public function testInterimContractAlsoReceivesCredit(): void
    {
        $contract = new Contract()
            ->setName('Interim')
            ->setTrackingMode('TIME')
            ->setWeeklyHours(35)
        ;

        self::assertSame(7 * 60, $this->resolver->resolveVirtualCredit($contract, new DateTimeImmutable('2025-07-14')));
    }

    public function testEffectiveDailyMinutesReturnsActualWhenGreaterThanReference(): void
    {
        $contract = $this->build39hContract();
        // 10h worked on a férié Monday with 39h contract (ref = 8h)
        self::assertSame(600, $this->resolver->resolveEffectiveDailyMinutes($contract, new DateTimeImmutable('2025-07-14'), 600));
    }

    public function testEffectiveDailyMinutesReturnsReferenceWhenActualLower(): void
    {
        $contract = $this->build39hContract();
        // 4h worked on a férié Monday with 39h contract (ref = 8h) → 8h
        self::assertSame(8 * 60, $this->resolver->resolveEffectiveDailyMinutes($contract, new DateTimeImmutable('2025-07-14'), 240));
    }

    public function testEffectiveDailyMinutesDelegatesWhenRuleDoesNotApply(): void
    {
        $contract = $this->build39hContract();
        // Non-holiday day: rule does not apply, return actual
        self::assertSame(420, $this->resolver->resolveEffectiveDailyMinutes($contract, new DateTimeImmutable('2025-07-07'), 420));
    }

    public function testFallsBackGracefullyWhenHolidayServiceFails(): void
    {
        $failingService = $this->createStub(PublicHolidayServiceInterface::class);
        $failingService->method('getHolidaysDayByYears')->willThrowException(new RuntimeException('boom'));

        $resolver = new HolidayVirtualHoursResolver(
            new DailyReferenceMinutesResolver(),
            $failingService,
            $this->createStub(EmployeeContractResolver::class),
        );

        self::assertSame(0, $resolver->resolveVirtualCredit($this->build35hContract(), new DateTimeImmutable('2025-07-14')));
    }

    public function testScheduledWorkdayGetsCreditOnHoliday(): void
    {
        // 4h contract, schedule Mon 2h + Thu 2h
        $contract = new Contract()->setName('4h')->setTrackingMode('TIME')->setWeeklyHours(4);
        // Holiday 2025-07-14 is a Monday → 120 min credit
        self::assertSame(120, $this->resolver->resolveVirtualCredit($contract, new DateTimeImmutable('2025-07-14'), false, [1 => 120, 4 => 120]));
    }

    public function testUnscheduledWorkdayGetsZeroOnHoliday(): void
    {
        $contract = new Contract()->setName('4h')->setTrackingMode('TIME')->setWeeklyHours(4);
        // Holiday 2025-07-14 is a Monday but schedule only Tue+Fri → 0
        self::assertSame(0, $this->resolver->resolveVirtualCredit($contract, new DateTimeImmutable('2025-07-14'), false, [2 => 120, 5 => 120]));
    }

    private function build35hContract(): Contract
    {
        return new Contract()
            ->setName('35h')
            ->setTrackingMode('TIME')
            ->setWeeklyHours(35)
        ;
    }

    private function build39hContract(): Contract
    {
        return new Contract()
            ->setName('39h')
            ->setTrackingMode('TIME')
            ->setWeeklyHours(39)
        ;
    }
}
