<?php

namespace App\Services\Leave;

use App\Models\Holiday;
use App\Models\LeaveType;
use App\Models\User;
use App\Models\WeekoffRule;
use App\Services\Attendance\WeekoffResolver;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Support\Collection;

class LeaveDayCalculator
{
    public function __construct(
        protected ?WeekoffResolver $weekoffResolver = null
    ) {}

    /**
     * @return array{calendar_days: float, chargeable_days: float, breakdown: array<int, array{date: string, is_weekend: bool, is_holiday: bool, chargeable: bool}>}
     */
    public function calculate(User $user, LeaveType $leaveType, Carbon $startDate, Carbon $endDate, bool $isHalfDay): array
    {
        $start = $startDate->copy()->startOfDay();
        $end = $endDate->copy()->startOfDay();
        $resolver = $this->resolver();
        $companyId = (int) ($user->company_id ?? 0);
        $userId = (int) $user->id;

        $calendarDays = max(1, $start->diffInDays($end) + 1);
        if ($isHalfDay && $start->isSameDay($end)) {
            return [
                'calendar_days' => 1.0,
                'chargeable_days' => 0.5,
                'breakdown' => [[
                    'date' => $start->toDateString(),
                    'is_weekend' => $resolver->isWeekoff($companyId, $userId, $start),
                    'is_holiday' => $this->holidayDates($user, $start, $end)->contains($start->toDateString()),
                    'chargeable' => true,
                ]],
            ];
        }

        $holidayDates = $this->holidayDates($user, $start, $end);
        $weekoffMap = $resolver->dayTypeMap($companyId, $userId, $start, $end);
        $weekendPolicy = $leaveType->weekend_policy ?? 'exclude';
        $holidayPolicy = $leaveType->holiday_policy ?? 'exclude';
        $sandwich = (bool) optional($leaveType->rule)->sandwich_rule;

        $chargeable = 0.0;
        $breakdown = [];

        foreach (CarbonPeriod::create($start, $end) as $date) {
            /** @var Carbon $date */
            $isWeekend = ($weekoffMap[$date->toDateString()] ?? null) === WeekoffRule::DAY_WEEKLY_OFF;
            $isHoliday = $holidayDates->contains($date->toDateString());

            $isChargeable = true;
            if (! $sandwich) {
                if ($isWeekend && $weekendPolicy === 'exclude') {
                    $isChargeable = false;
                }
                if ($isHoliday && $holidayPolicy === 'exclude') {
                    $isChargeable = false;
                }
            }

            if ($isChargeable) {
                $chargeable += 1.0;
            }

            $breakdown[] = [
                'date' => $date->toDateString(),
                'is_weekend' => $isWeekend,
                'is_holiday' => $isHoliday,
                'chargeable' => $isChargeable,
            ];
        }

        if ($start->isSameDay($end) && ! $isHalfDay && $chargeable < 1.0) {
            $chargeable = 1.0;
            if ($breakdown !== []) {
                $breakdown[0]['chargeable'] = true;
            } else {
                $breakdown[] = [
                    'date' => $start->toDateString(),
                    'is_weekend' => $resolver->isWeekoff($companyId, $userId, $start),
                    'is_holiday' => $holidayDates->contains($start->toDateString()),
                    'chargeable' => true,
                ];
            }
        }

        return [
            'calendar_days' => (float) $calendarDays,
            'chargeable_days' => (float) $chargeable,
            'breakdown' => $breakdown,
        ];
    }

    protected function resolver(): WeekoffResolver
    {
        return $this->weekoffResolver ??= app(WeekoffResolver::class);
    }

    private function holidayDates(User $user, Carbon $start, Carbon $end): Collection
    {
        $companyId = (int) ($user->company_id ?? 0);
        if ($companyId <= 0) {
            return collect();
        }

        return Holiday::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->whereBetween('date', [$start->toDateString(), $end->toDateString()])
            ->pluck('date')
            ->map(fn ($date) => Carbon::parse($date)->toDateString())
            ->unique()
            ->values();
    }
}

