<?php

declare(strict_types=1);

namespace App\Service\WorkHours;

use App\Entity\Contract;
use App\Entity\Employee;
use App\Enum\TrackingMode;
use App\Service\Contracts\EmployeeContractResolver;
use App\Service\PublicHolidayServiceInterface;
use DateTimeImmutable;
use Throwable;

/**
 * Applies the business rule: a public holiday from Monday to Friday, for any
 * non-Forfait contract, credits the contractually expected daily hours.
 * If the employee has also entered hours that day, the effective total is the
 * max between entered minutes and the contractual reference.
 */
final readonly class HolidayVirtualHoursResolver
{
    public function __construct(
        private DailyReferenceMinutesResolver $dailyReferenceResolver,
        private PublicHolidayServiceInterface $publicHolidayService,
        private EmployeeContractResolver $contractResolver,
    ) {}

    /**
     * Returns the effective daily minutes to count for RTT and weekly total
     * aggregation, applying the holiday credit when applicable.
     *
     * If an absence is declared on the day, the absence dictates the credit
     * (via WorkedHoursCreditPolicy) and the holiday virtual rule is bypassed —
     * $actualMinutes already includes the absence credit.
     *
     * @param null|array<int, int> $workDaysMinutes per-employee schedule (iso day → minutes)
     */
    public function resolveEffectiveDailyMinutes(
        ?Contract $contract,
        DateTimeImmutable $date,
        int $actualMinutes,
        bool $hasAbsenceOnDate = false,
        ?array $workDaysMinutes = null,
    ): int {
        $reference = $this->resolveVirtualCredit($contract, $date, $hasAbsenceOnDate, $workDaysMinutes);
        if (0 === $reference) {
            return $actualMinutes;
        }

        return max($actualMinutes, $reference);
    }

    /**
     * Returns the virtual credit (reference minutes) alone — 0 if the rule
     * does not apply (weekend, non-holiday, Forfait contract, absence declared,
     * or employee schedule indicates a non-working day). Used by the frontend.
     *
     * @param null|array<int, int> $workDaysMinutes per-employee schedule (iso day → minutes)
     */
    public function resolveVirtualCredit(
        ?Contract $contract,
        DateTimeImmutable $date,
        bool $hasAbsenceOnDate = false,
        ?array $workDaysMinutes = null,
    ): int {
        if ($hasAbsenceOnDate) {
            return 0;
        }

        $isoDay = (int) $date->format('N');
        if ($isoDay >= 6) {
            return 0;
        }

        if (null === $contract) {
            return 0;
        }

        if (TrackingMode::PRESENCE->value === $contract->getTrackingMode()) {
            return 0;
        }

        if (!$this->isPublicHoliday($date)) {
            return 0;
        }

        return $this->dailyReferenceResolver->resolve($contract->getWeeklyHours(), $isoDay, $workDaysMinutes);
    }

    /**
     * Convenience helper: resolves the schedule internally for a single employee/date.
     * Used by callers that have an Employee in hand (e.g. DayContext, LeaveRecap).
     */
    public function resolveVirtualCreditForEmployee(
        Employee $employee,
        DateTimeImmutable $date,
        bool $hasAbsenceOnDate = false,
    ): int {
        $contract        = $this->contractResolver->resolveForEmployeeAndDate($employee, $date);
        $workDaysMinutes = $this->contractResolver->resolveWorkDaysMinutesForEmployeeAndDate($employee, $date);

        return $this->resolveVirtualCredit($contract, $date, $hasAbsenceOnDate, $workDaysMinutes);
    }

    private function isPublicHoliday(DateTimeImmutable $date): bool
    {
        try {
            $holidays = $this->publicHolidayService->getHolidaysDayByYears('metropole', $date->format('Y'));
        } catch (Throwable) {
            return false;
        }

        return isset($holidays[$date->format('Y-m-d')]);
    }
}
