<?php

namespace Tests\Tenant;

use App\ScheduledTasks\ScheduledTaskClock;
use Illuminate\Support\Carbon;
use Mtc\MercuryDataModels\ScheduledTask;
use Tests\TenantTestCase;

class ScheduledTaskClockTest extends TenantTestCase
{
    private ScheduledTaskClock $clock;

    #[\Override]
    protected function setUp(): void
    {
        parent::setUp();
        $this->clock = new ScheduledTaskClock();
    }

    #[\Override]
    protected function tearDown(): void
    {
        Carbon::setTestNow();
        parent::tearDown();
    }

    private function task(?array $rules, array $attributes = []): ScheduledTask
    {
        return ScheduledTask::factory()->make(array_merge(['schedule_rules' => $rules], $attributes));
    }

    public function testDailyWithMultipleTimesPicksNextFutureTime(): void
    {
        Carbon::setTestNow('2026-07-08 09:00:00');
        $task = $this->task([
            ['frequency' => 'daily', 'time' => '08:00'],
            ['frequency' => 'daily', 'time' => '17:30'],
        ]);

        $this->assertEquals(
            '2026-07-08 17:30:00',
            $this->clock->nextDueAt($task, Carbon::now())->format('Y-m-d H:i:s')
        );

        Carbon::setTestNow('2026-07-08 18:00:00');
        $this->assertEquals(
            '2026-07-09 08:00:00',
            $this->clock->nextDueAt($task, Carbon::now())->format('Y-m-d H:i:s')
        );
    }

    public function testHeterogeneousRulesInOneTask(): void
    {
        // The exact case: daily 08:00, daily 14:00, weekly Sunday 12:00, monthly 21st 09:00
        $task = $this->task([
            ['frequency' => 'daily', 'time' => '08:00'],
            ['frequency' => 'daily', 'time' => '14:00'],
            ['frequency' => 'weekly', 'day_of_week' => 7, 'time' => '12:00'],
            ['frequency' => 'monthly', 'day_of_month' => 21, 'time' => '09:00'],
        ]);

        // A plain weekday (Wed 2026-07-08): only the two daily rules apply
        $this->assertEquals(
            ['08:00', '14:00'],
            $this->clock->occurrencesForDate($task, Carbon::parse('2026-07-08'))
                ->map(fn (Carbon $o) => $o->format('H:i'))->toArray()
        );

        // A Sunday (2026-07-12): the two daily rules plus the weekly one
        $this->assertEquals(
            ['08:00', '12:00', '14:00'],
            $this->clock->occurrencesForDate($task, Carbon::parse('2026-07-12'))
                ->map(fn (Carbon $o) => $o->format('H:i'))->toArray()
        );

        // The 21st (2026-07-21, a Tuesday): the two daily rules plus the monthly one
        $this->assertEquals(
            ['08:00', '09:00', '14:00'],
            $this->clock->occurrencesForDate($task, Carbon::parse('2026-07-21'))
                ->map(fn (Carbon $o) => $o->format('H:i'))->toArray()
        );
    }

    public function testDuplicateTimesAcrossRulesAreDeduped(): void
    {
        $task = $this->task([
            ['frequency' => 'daily', 'time' => '08:00'],
            ['frequency' => 'weekly', 'day_of_week' => 3, 'time' => '08:00'],
        ]);

        // Wednesday: both rules fire at 08:00, but only one occurrence is produced
        $this->assertCount(1, $this->clock->occurrencesForDate($task, Carbon::parse('2026-07-08')));
    }

    public function testWeekdaysRunsMondayToFridayOnly(): void
    {
        $task = $this->task([['frequency' => 'weekdays', 'time' => '09:00']]);

        // 2026-07-10 Fri, 2026-07-11 Sat, 2026-07-12 Sun, 2026-07-13 Mon
        $this->assertCount(1, $this->clock->occurrencesForDate($task, Carbon::parse('2026-07-10')));
        $this->assertTrue($this->clock->occurrencesForDate($task, Carbon::parse('2026-07-11'))->isEmpty());
        $this->assertTrue($this->clock->occurrencesForDate($task, Carbon::parse('2026-07-12'))->isEmpty());
        $this->assertCount(1, $this->clock->occurrencesForDate($task, Carbon::parse('2026-07-13')));

        // Next due from Saturday skips to Monday
        Carbon::setTestNow('2026-07-11 10:00:00');
        $this->assertEquals(
            '2026-07-13 09:00:00',
            $this->clock->nextDueAt($task, Carbon::now())->format('Y-m-d H:i:s')
        );
    }

    public function testWeeklyRunsOnlyOnItsIsoWeekday(): void
    {
        $task = $this->task([['frequency' => 'weekly', 'day_of_week' => 1, 'time' => '06:00']]);

        $this->assertTrue($this->clock->occurrencesForDate($task, Carbon::parse('2026-07-08'))->isEmpty());
        $this->assertCount(1, $this->clock->occurrencesForDate($task, Carbon::parse('2026-07-06')));

        Carbon::setTestNow('2026-07-08 09:00:00');
        $this->assertEquals(
            '2026-07-13 06:00:00',
            $this->clock->nextDueAt($task, Carbon::now())->format('Y-m-d H:i:s')
        );
    }

    public function testMonthlyClampsDayToMonthLength(): void
    {
        $task = $this->task([['frequency' => 'monthly', 'day_of_month' => 31, 'time' => '05:00']]);

        $this->assertCount(1, $this->clock->occurrencesForDate($task, Carbon::parse('2026-06-30')));
        $this->assertTrue($this->clock->occurrencesForDate($task, Carbon::parse('2026-06-29'))->isEmpty());

        Carbon::setTestNow('2026-02-01 00:00:00');
        $this->assertEquals(
            '2026-02-28 05:00:00',
            $this->clock->nextDueAt($task, Carbon::now())->format('Y-m-d H:i:s')
        );
    }

    public function testBiweeklySkipsTheOffWeek(): void
    {
        $task = $this->task(
            [['frequency' => 'biweekly', 'day_of_week' => 5, 'time' => '07:00']],
            ['last_run_at' => Carbon::parse('2026-07-03 07:00:00')]
        );

        $this->assertTrue($this->clock->occurrencesForDate($task, Carbon::parse('2026-07-10'))->isEmpty());
        $this->assertCount(1, $this->clock->occurrencesForDate($task, Carbon::parse('2026-07-17')));
    }

    public function testUnschedulableTasksHaveNoNextDue(): void
    {
        Carbon::setTestNow('2026-07-08 09:00:00');

        $paused = $this->task([['frequency' => 'daily', 'time' => '08:00']], ['enabled' => false]);
        $noRules = $this->task([]);
        $dependency = $this->task(null, [
            'trigger_type' => ScheduledTask::TRIGGER_AFTER_TASK,
            'schedule_rules' => null,
        ]);

        $this->assertNull($this->clock->nextDueAt($paused, Carbon::now()));
        $this->assertNull($this->clock->nextDueAt($noRules, Carbon::now()));
        $this->assertNull($this->clock->nextDueAt($dependency, Carbon::now()));
    }
}
