<?php

namespace App\Services\Reports;

use App\Models\ReportSchedule;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;

class ScheduleNextRunCalculator
{
    public function calculate(ReportSchedule $schedule, ?CarbonInterface $from = null): CarbonInterface
    {
        $timezone = $schedule->timezone ?: config('app.timezone', 'Europe/Istanbul');
        $now = CarbonImmutable::instance($from ?: now())->setTimezone($timezone);
        $time = $this->timeParts($schedule->daily_time ?: '09:00');

        $next = match ($schedule->frequency) {
            'daily' => $this->daily($now, $time),
            'weekly' => $this->weekly($now, $time, (int) ($schedule->weekly_day ?: 1)),
            'monthly' => $this->monthly($now, $time, (int) ($schedule->monthly_day ?: 1)),
            default => $this->daily($now, $time),
        };

        return $next->setTimezone(config('app.timezone', 'Europe/Istanbul'));
    }

    /**
     * @param array{0:int,1:int} $time
     */
    private function daily(CarbonImmutable $now, array $time): CarbonImmutable
    {
        $candidate = $now->setTime($time[0], $time[1]);

        return $candidate->greaterThan($now) ? $candidate : $candidate->addDay();
    }

    /**
     * @param array{0:int,1:int} $time
     */
    private function weekly(CarbonImmutable $now, array $time, int $day): CarbonImmutable
    {
        $day = max(1, min(7, $day));
        $candidate = $now->setTime($time[0], $time[1]);

        while ($candidate->dayOfWeekIso !== $day || $candidate->lessThanOrEqualTo($now)) {
            $candidate = $candidate->addDay();
        }

        return $candidate;
    }

    /**
     * @param array{0:int,1:int} $time
     */
    private function monthly(CarbonImmutable $now, array $time, int $day): CarbonImmutable
    {
        $day = max(1, min(31, $day));
        $candidate = $this->dateWithSafeDay($now, $day)->setTime($time[0], $time[1]);

        if ($candidate->lessThanOrEqualTo($now)) {
            $candidate = $this->dateWithSafeDay($now->addMonthNoOverflow(), $day)->setTime($time[0], $time[1]);
        }

        return $candidate;
    }

    private function dateWithSafeDay(CarbonImmutable $date, int $day): CarbonImmutable
    {
        $safeDay = min($day, $date->daysInMonth);

        return $date->startOfMonth()->addDays($safeDay - 1);
    }

    /**
     * @return array{0:int,1:int}
     */
    private function timeParts(string $time): array
    {
        [$hour, $minute] = array_pad(explode(':', $time), 2, 0);

        return [(int) $hour, (int) $minute];
    }
}
