<?php

declare(strict_types=1);

namespace App\Tests\Unit\Service;

use App\Module\Absence\Domain\Enum\HalfDay;
use App\Module\Absence\Domain\Service\AbsenceDayCalculator;
use App\Module\Absence\Domain\Service\PublicHolidayProvider;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;

/**
 * @internal
 */
class AbsenceDayCalculatorTest extends TestCase
{
    private AbsenceDayCalculator $calculator;

    protected function setUp(): void
    {
        $this->calculator = new AbsenceDayCalculator(new PublicHolidayProvider());
    }

    public function testFullWeekIsFiveWorkingDays(): void
    {
        // Mon 2026-06-01 to Fri 2026-06-05, no holidays that week
        self::assertSame(5.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-01'),
            new DateTimeImmutable('2026-06-05'),
        ));
    }

    public function testWeekendIsSkipped(): void
    {
        // Fri 2026-06-05 to Mon 2026-06-08 => Fri + Mon = 2
        self::assertSame(2.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-05'),
            new DateTimeImmutable('2026-06-08'),
        ));
    }

    public function testHolidayIsSkipped(): void
    {
        // Thu 2026-05-07 to Fri 2026-05-08 (Victoire 1945) => Thu only = 1
        self::assertSame(1.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-05-07'),
            new DateTimeImmutable('2026-05-08'),
        ));
    }

    public function testHalfDayStartSubtractsHalf(): void
    {
        self::assertSame(4.5, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-01'),
            new DateTimeImmutable('2026-06-05'),
            HalfDay::Afternoon,
        ));
    }

    public function testBothHalfDaysSubtractOne(): void
    {
        self::assertSame(4.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-01'),
            new DateTimeImmutable('2026-06-05'),
            HalfDay::Afternoon,
            HalfDay::Morning,
        ));
    }

    public function testSingleFullDayCountsOne(): void
    {
        // Most common request: a single full working day = 1.0 (no half-day).
        self::assertSame(1.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-01'),
            new DateTimeImmutable('2026-06-01'),
        ));
    }

    public function testSingleHalfDay(): void
    {
        self::assertSame(0.5, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-01'),
            new DateTimeImmutable('2026-06-01'),
            HalfDay::Morning,
        ));
    }

    public function testHalfDayEndOnlySubtractsHalf(): void
    {
        // Mirror of testHalfDayStartSubtractsHalf: end half-day only, on a
        // counted multi-day range => 5 - 0.5 = 4.5.
        self::assertSame(4.5, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-01'),
            new DateTimeImmutable('2026-06-05'),
            null,
            HalfDay::Morning,
        ));
    }

    public function testEndBeforeStartCountsZero(): void
    {
        // Inverted range guard (end < start) => 0, never negative.
        self::assertSame(0.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-05'),
            new DateTimeImmutable('2026-06-01'),
        ));
    }

    public function testSingleDayOnWeekendCountsZero(): void
    {
        // Saturday, jours ouvrés: not counted, even with a half-day => 0.
        self::assertSame(0.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-06'),
            new DateTimeImmutable('2026-06-06'),
            HalfDay::Morning,
            HalfDay::Morning,
        ));
    }

    public function testSingleDayOnHolidayCountsZero(): void
    {
        // Fri 2026-05-08 (Victoire 1945): public holiday => 0.
        self::assertSame(0.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-05-08'),
            new DateTimeImmutable('2026-05-08'),
        ));
    }

    public function testHalfDayOnSaturdayCountedInOpenDays(): void
    {
        // Fri + Sat in "ouvrables" mode = 2; the end half-day sits on Saturday,
        // which IS counted here => 2 - 0.5 = 1.5.
        self::assertSame(1.5, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-05'),
            new DateTimeImmutable('2026-06-06'),
            null,
            HalfDay::Morning,
            false,
        ));
    }

    public function testSingleDayWithBothHalfDaysIsStillHalf(): void
    {
        // Real-world single-day request: the form mirrors the start half-day
        // onto the end (same day), so both boundaries carry a half-day. It must
        // still count as 0.5, not 0 (the two boundaries collapse onto one day).
        self::assertSame(0.5, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-01'),
            new DateTimeImmutable('2026-06-01'),
            HalfDay::Morning,
            HalfDay::Morning,
        ));
    }

    public function testHalfDayOnNonCountedStartIsIgnored(): void
    {
        // Sat 2026-06-06 → Mon 2026-06-08, jours ouvrés : Sat & Sun skipped, Mon = 1.
        // startHalfDay sits on Saturday (not counted) => must NOT subtract 0.5.
        self::assertSame(1.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-06'),
            new DateTimeImmutable('2026-06-08'),
            HalfDay::Afternoon,
        ));
    }

    public function testHalfDayOnNonCountedEndIsIgnored(): void
    {
        // Fri 2026-06-05 → Sun 2026-06-07, jours ouvrés : Fri = 1, weekend skipped.
        // endHalfDay sits on Sunday (not counted) => must NOT subtract 0.5.
        self::assertSame(1.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-05'),
            new DateTimeImmutable('2026-06-07'),
            null,
            HalfDay::Morning,
        ));
    }

    public function testWorkingDaysVsOpenDays(): void
    {
        // Fri 2026-06-05 to Mon 2026-06-08, "ouvrables" includes Saturday
        // => Fri + Sat + Mon = 3 (Sunday always skipped)
        self::assertSame(3.0, $this->calculator->countWorkingDays(
            new DateTimeImmutable('2026-06-05'),
            new DateTimeImmutable('2026-06-08'),
            null,
            null,
            false,
        ));
    }
}
