<?php

declare(strict_types=1);

namespace App\Service\Contracts;

use App\Entity\Contract;
use App\Entity\Employee;
use App\Enum\ContractNature;
use App\Repository\EmployeeContractPeriodRepository;
use DateTimeImmutable;

readonly class EmployeeContractResolver
{
    public function __construct(
        private EmployeeContractPeriodRepository $periodRepository,
    ) {}

    public function resolveForEmployeeAndDate(Employee $employee, DateTimeImmutable $date): ?Contract
    {
        $period = $this->periodRepository->findOneCoveringDate($employee, $date);

        return $period?->getContract();
    }

    /**
     * @return null|array<int, int> workDaysHours (iso day → minutes) for the contract period active on $date
     */
    public function resolveWorkDaysMinutesForEmployeeAndDate(Employee $employee, DateTimeImmutable $date): ?array
    {
        $period = $this->periodRepository->findOneCoveringDate($employee, $date);
        $raw    = $period?->getWorkDaysHours();
        if (null === $raw) {
            return null;
        }

        return $this->normalizeWorkDaysMinutes($raw);
    }

    public function resolveIsDriverForEmployeeAndDate(Employee $employee, DateTimeImmutable $date): bool
    {
        $period = $this->periodRepository->findOneCoveringDate($employee, $date);

        return $period?->getIsDriver() ?? false;
    }

    /**
     * @param list<Employee> $employees
     * @param list<string>   $days
     *
     * @return array<int, array<string, bool>>
     */
    public function resolveIsDriverForEmployeesAndDays(array $employees, array $days): array
    {
        $resolved = [];
        if ([] === $employees || [] === $days) {
            return $resolved;
        }

        foreach ($employees as $employee) {
            $employeeId = $employee->getId();
            if (!$employeeId) {
                continue;
            }

            foreach ($days as $day) {
                $resolved[$employeeId][$day] = false;
            }
        }

        $from    = new DateTimeImmutable(min($days));
        $to      = new DateTimeImmutable(max($days));
        $periods = $this->periodRepository->findByEmployeesAndDateRange($employees, $from, $to);
        foreach ($periods as $period) {
            $employeeId = $period->getEmployee()?->getId();
            if (!$employeeId) {
                continue;
            }

            $start    = $period->getStartDate()->format('Y-m-d');
            $end      = $period->getEndDate()?->format('Y-m-d') ?? '9999-12-31';
            $isDriver = $period->getIsDriver();
            foreach ($days as $day) {
                if ($day < $start || $day > $end) {
                    continue;
                }
                $resolved[$employeeId][$day] = $isDriver;
            }
        }

        return $resolved;
    }

    public function resolveNatureForEmployeeAndDate(Employee $employee, DateTimeImmutable $date): ContractNature
    {
        $period = $this->periodRepository->findOneCoveringDate($employee, $date);

        return $period?->getContractNatureEnum() ?? ContractNature::CDI;
    }

    /**
     * @param list<Employee> $employees
     * @param list<string>   $days
     *
     * @return array<int, array<string, null|array<int, int>>>
     */
    public function resolveWorkDaysMinutesForEmployeesAndDays(array $employees, array $days): array
    {
        $resolved = [];
        if ([] === $employees || [] === $days) {
            return $resolved;
        }

        foreach ($employees as $employee) {
            $employeeId = $employee->getId();
            if (!$employeeId) {
                continue;
            }
            foreach ($days as $day) {
                $resolved[$employeeId][$day] = null;
            }
        }

        $from    = new DateTimeImmutable(min($days));
        $to      = new DateTimeImmutable(max($days));
        $periods = $this->periodRepository->findByEmployeesAndDateRange($employees, $from, $to);
        foreach ($periods as $period) {
            $employeeId = $period->getEmployee()?->getId();
            if (!$employeeId) {
                continue;
            }

            $raw = $period->getWorkDaysHours();
            if (null === $raw) {
                continue;
            }

            $normalized = $this->normalizeWorkDaysMinutes($raw);
            $start      = $period->getStartDate()->format('Y-m-d');
            $end        = $period->getEndDate()?->format('Y-m-d') ?? '9999-12-31';
            foreach ($days as $day) {
                if ($day < $start || $day > $end) {
                    continue;
                }
                $resolved[$employeeId][$day] = $normalized;
            }
        }

        return $resolved;
    }

    /**
     * @param list<Employee> $employees
     * @param list<string>   $days
     *
     * @return array<int, array<string, ?Contract>>
     */
    public function resolveForEmployeesAndDays(array $employees, array $days): array
    {
        $resolved = [];
        if ([] === $employees || [] === $days) {
            return $resolved;
        }

        foreach ($employees as $employee) {
            $employeeId = $employee->getId();
            if (!$employeeId) {
                continue;
            }

            foreach ($days as $day) {
                $resolved[$employeeId][$day] = null;
            }
        }

        $from    = new DateTimeImmutable(min($days));
        $to      = new DateTimeImmutable(max($days));
        $periods = $this->periodRepository->findByEmployeesAndDateRange($employees, $from, $to);
        foreach ($periods as $period) {
            $employeeId = $period->getEmployee()?->getId();
            $contract   = $period->getContract();
            if (!$employeeId || null === $contract) {
                continue;
            }

            $start = $period->getStartDate()->format('Y-m-d');
            $end   = $period->getEndDate()?->format('Y-m-d') ?? '9999-12-31';
            foreach ($days as $day) {
                if ($day < $start || $day > $end) {
                    continue;
                }
                $resolved[$employeeId][$day] = $contract;
            }
        }

        return $resolved;
    }

    /**
     * @param list<Employee> $employees
     * @param list<string>   $days
     *
     * @return array<int, array<string, ContractNature>>
     */
    public function resolveNaturesForEmployeesAndDays(array $employees, array $days): array
    {
        $resolved = [];
        if ([] === $employees || [] === $days) {
            return $resolved;
        }

        foreach ($employees as $employee) {
            $employeeId = $employee->getId();
            if (!$employeeId) {
                continue;
            }

            foreach ($days as $day) {
                $resolved[$employeeId][$day] = ContractNature::CDI;
            }
        }

        $from    = new DateTimeImmutable(min($days));
        $to      = new DateTimeImmutable(max($days));
        $periods = $this->periodRepository->findByEmployeesAndDateRange($employees, $from, $to);
        foreach ($periods as $period) {
            $employeeId = $period->getEmployee()?->getId();
            if (!$employeeId) {
                continue;
            }

            $start  = $period->getStartDate()->format('Y-m-d');
            $end    = $period->getEndDate()?->format('Y-m-d') ?? '9999-12-31';
            $nature = $period->getContractNatureEnum();
            foreach ($days as $day) {
                if ($day < $start || $day > $end) {
                    continue;
                }
                $resolved[$employeeId][$day] = $nature;
            }
        }

        return $resolved;
    }

    /**
     * @param array<int|string, mixed> $raw
     *
     * @return array<int, int>
     */
    private function normalizeWorkDaysMinutes(array $raw): array
    {
        $result = [];
        foreach ($raw as $key => $value) {
            $iso = (int) $key;
            if ($iso < 1 || $iso > 5) {
                continue;
            }
            $result[$iso] = (int) $value;
        }

        return $result;
    }
}
