<?php

namespace Tests\Feature\Controllers;

use App\Models\ScheduledSlotProfile;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Queue;
use Mtc\MercuryDataModels\ScheduledTask;
use Mtc\MercuryDataModels\ScheduledTaskRun;
use Database\Seeders\Global\ScheduledTaskPermissionSeed;
use Tests\DatabaseTestCase;
use Tests\UserForTenant;

class ScheduledTaskControllerTest extends DatabaseTestCase
{
    use UserForTenant;

    #[\Override]
    protected function setUp(): void
    {
        parent::setUp();
        Queue::fake();
        $this->withHeader('X-Tenant', tenant('id'));
    }

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

    public function testIndexListsTasksWithTriggerSummary(): void
    {
        $this->actAsAuthorised();

        ScheduledTask::factory()->create(['name' => 'Nightly export', 'schedule_rules' => [['frequency' => 'daily', 'time' => '08:00'], ['frequency' => 'daily', 'time' => '17:30']]]);

        $response = $this->getJson('/api/settings/scheduled-tasks');

        $response->assertOk();
        $this->assertEquals('Nightly export', $response->json('data.0.name'));
        $this->assertEquals('Daily 08:00, Daily 17:30', $response->json('data.0.trigger_summary'));
    }

    public function testPermissionIsRequiredOnEveryEndpoint(): void
    {
        $this->actAsUnauthorised();

        $task = ScheduledTask::factory()->create();

        $this->getJson('/api/settings/scheduled-tasks')->assertForbidden();
        $this->postJson('/api/settings/scheduled-tasks', ['name' => 'x'])->assertForbidden();
        $this->putJson("/api/settings/scheduled-tasks/{$task->id}", [])->assertForbidden();
        $this->deleteJson("/api/settings/scheduled-tasks/{$task->id}")->assertForbidden();
        $this->postJson("/api/settings/scheduled-tasks/{$task->id}/run")->assertForbidden();
    }

    public function testUpdateValidatesActionsAgainstTheRegistry(): void
    {
        $this->actAsAuthorised();

        $task = ScheduledTask::factory()->create();

        $response = $this->putJson("/api/settings/scheduled-tasks/{$task->id}", [
            'name' => 'Broken task',
            'trigger_type' => ScheduledTask::TRIGGER_SCHEDULE,
            'schedule_rules' => [['frequency' => 'daily', 'time' => '08:00']],
            'actions' => [
                ['action_type' => 'not-a-real-action', 'config' => []],
            ],
        ]);

        $response->assertUnprocessable();
        $this->assertArrayHasKey('actions.0.action_type', $response->json('errors'));
    }

    public function testUpdateStoresScheduleActionsAndRematerialisesToday(): void
    {
        Carbon::setTestNow('2026-07-08 07:00:00');
        $this->actAsAuthorised();

        $task = ScheduledTask::factory()->create(['schedule_rules' => [['frequency' => 'daily', 'time' => '06:00']]]);
        $exportMap = \App\Models\ExportMap::factory()->create(['schedule_enabled' => false]);

        $response = $this->putJson("/api/settings/scheduled-tasks/{$task->id}", [
            'name' => 'Feed then more',
            'enabled' => true,
            'overlap_policy' => ScheduledTask::OVERLAP_QUEUE,
            'trigger_type' => ScheduledTask::TRIGGER_SCHEDULE,
            'schedule_rules' => [['frequency' => 'daily', 'time' => '09:00'], ['frequency' => 'daily', 'time' => '18:00']],
            'actions' => [
                [
                    'action_type' => 'export-map',
                    'config' => ['export_map_id' => $exportMap->id],
                    'run_when' => 'on_success',
                    'timeout_seconds' => 120,
                ],
            ],
        ]);

        $response->assertOk();

        $task->refresh();
        $this->assertEquals(['09:00', '18:00'], collect($task->schedule_rules)->pluck('time')->toArray());
        $this->assertEquals('2026-07-08 09:00', $task->next_due_at->format('Y-m-d H:i'));
        $this->assertCount(1, $task->actions);
        $this->assertEquals($exportMap->id, $task->actions->first()->config['export_map_id']);

        // Today's remaining queue reflects the new times
        $this->assertEquals(
            ['2026-07-08 09:00', '2026-07-08 18:00'],
            $task->runs()->orderBy('scheduled_for')->get()
                ->map(fn (ScheduledTaskRun $run) => $run->scheduled_for->format('Y-m-d H:i'))
                ->toArray()
        );

        // The tenant's slot profile was rebuilt centrally (scoped to this tenant, as the
        // central table is shared across tenants)
        $this->assertEquals(
            2,
            ScheduledSlotProfile::query()->where('tenant_id', tenant('id'))->count()
        );
    }

    public function testFirstActionConditionIsForcedToAlways(): void
    {
        $this->actAsAuthorised();

        $exportMap = \App\Models\ExportMap::factory()->create(['schedule_enabled' => false]);
        $task = ScheduledTask::factory()->create();

        $this->putJson("/api/settings/scheduled-tasks/{$task->id}", [
            'name' => $task->name,
            'trigger_type' => ScheduledTask::TRIGGER_SCHEDULE,
            'schedule_rules' => [['frequency' => 'daily', 'time' => '08:00']],
            'actions' => [
                // Deliberately try to set the first action to on_failure (a footgun)
                ['action_type' => 'export-map', 'config' => ['export_map_id' => $exportMap->id], 'run_when' => 'on_failure'],
                ['action_type' => 'export-map', 'config' => ['export_map_id' => $exportMap->id], 'run_when' => 'on_failure'],
            ],
        ])->assertOk();

        $actions = $task->fresh()->actions;
        $this->assertEquals('always', $actions[0]->run_when, 'First action is forced to always');
        $this->assertEquals('on_failure', $actions[1]->run_when, 'Later actions keep their condition');
    }

    public function testAfterTaskTriggerSavesWithoutScheduleRules(): void
    {
        $this->actAsAuthorised();

        $parent = ScheduledTask::factory()->create(['name' => 'Parent']);
        $child = ScheduledTask::factory()->create([
            'name' => 'Child',
            'schedule_rules' => [['frequency' => 'daily', 'time' => '08:00']],
        ]);

        $response = $this->putJson("/api/settings/scheduled-tasks/{$child->id}", [
            'name' => 'Child',
            'trigger_type' => ScheduledTask::TRIGGER_AFTER_TASK,
            'parent_task_id' => $parent->id,
            'parent_condition' => ScheduledTask::CONDITION_ON_SUCCESS,
            'schedule_rules' => [], // UI may still send an empty array; must not block saving
            'actions' => [],
        ]);

        $response->assertOk();
        $child->refresh();
        $this->assertEquals(ScheduledTask::TRIGGER_AFTER_TASK, $child->trigger_type);
        $this->assertEquals($parent->id, $child->parent_task_id);
        $this->assertNull($child->schedule_rules);
        $this->assertNull($child->next_due_at);
    }

    public function testPausingATaskSkipsTodaysPendingRunsAndClearsNextDue(): void
    {
        Carbon::setTestNow('2026-07-08 07:00:00');
        $this->actAsAuthorised();

        $task = ScheduledTask::factory()->create(['schedule_rules' => [['frequency' => 'daily', 'time' => '09:00']]]);
        $pending = ScheduledTaskRun::factory()->create([
            'scheduled_task_id' => $task->id,
            'scheduled_for' => Carbon::parse('2026-07-08 09:00:00'),
        ]);

        $this->putJson("/api/settings/scheduled-tasks/{$task->id}", [
            'name' => $task->name,
            'enabled' => false,
            'trigger_type' => ScheduledTask::TRIGGER_SCHEDULE,
            'schedule_rules' => [['frequency' => 'daily', 'time' => '09:00']],
            'actions' => [],
        ])->assertOk();

        $task->refresh();
        $this->assertFalse($task->enabled);
        $this->assertNull($task->next_due_at);
        $this->assertEquals(ScheduledTaskRun::STATUS_SKIPPED, $pending->fresh()->status);
        $this->assertStringContainsString('paused', $pending->fresh()->error);
    }

    public function testRunNowCreatesAndDispatchesAManualRun(): void
    {
        $this->actAsAuthorised();

        $task = ScheduledTask::factory()->create();

        $response = $this->postJson("/api/settings/scheduled-tasks/{$task->id}/run");

        $response->assertCreated();
        Queue::assertPushed(\App\Jobs\RunScheduledTaskJob::class, 1);
        $this->assertEquals(ScheduledTaskRun::TRIGGER_MANUAL, $task->runs()->first()->trigger);
    }

    public function testRetryOnlyWorksOnFailedRuns(): void
    {
        $this->actAsAuthorised();

        $task = ScheduledTask::factory()->create();
        $failed = ScheduledTaskRun::factory()->create([
            'scheduled_task_id' => $task->id,
            'status' => ScheduledTaskRun::STATUS_FAILED,
            'scheduled_for' => Carbon::now()->subHour(),
        ]);
        $completed = ScheduledTaskRun::factory()->create([
            'scheduled_task_id' => $task->id,
            'status' => ScheduledTaskRun::STATUS_COMPLETED,
            'scheduled_for' => Carbon::now()->subHours(2),
        ]);

        $this->postJson("/api/settings/scheduled-task-runs/{$completed->id}/retry")
            ->assertUnprocessable();

        $response = $this->postJson("/api/settings/scheduled-task-runs/{$failed->id}/retry");
        $response->assertCreated();
        $this->assertEquals(ScheduledTaskRun::TRIGGER_RETRY, $response->json('data.trigger'));
        $this->assertEquals($failed->id, $response->json('data.triggered_by_run_id'));
        Queue::assertPushed(\App\Jobs\RunScheduledTaskJob::class, 1);
    }

    public function testSystemTasksRejectEditAndDelete(): void
    {
        $this->actAsAuthorised();

        $task = ScheduledTask::factory()->create(['managed_by' => ScheduledTask::MANAGED_BY_SYSTEM]);

        $this->putJson("/api/settings/scheduled-tasks/{$task->id}", [
            'name' => 'Hacked',
            'trigger_type' => ScheduledTask::TRIGGER_SCHEDULE,
            'schedule_rules' => [['frequency' => 'daily', 'time' => '09:00']],
        ])->assertForbidden();

        $this->deleteJson("/api/settings/scheduled-tasks/{$task->id}")->assertUnprocessable();

        // Run now is also blocked for system tasks
        $this->postJson("/api/settings/scheduled-tasks/{$task->id}/run")->assertUnprocessable();
    }

    public function testSlotLoadFlagsBusySlotsWithSuggestions(): void
    {
        $this->actAsAuthorised();
        config()->set('scheduled_tasks.slot_busy_threshold', 2);

        ScheduledSlotProfile::query()->insert([
            ['tenant_id' => 'other-a', 'slot' => '08:00', 'load' => 1.5, 'created_at' => now(), 'updated_at' => now()],
            ['tenant_id' => 'other-b', 'slot' => '08:00', 'load' => 1.0, 'created_at' => now(), 'updated_at' => now()],
        ]);

        $response = $this->getJson('/api/settings/scheduled-tasks/slot-load?times[]=08:00&times[]=13:15');

        $response->assertOk();
        $this->assertTrue($response->json('data.0.busy'));
        $this->assertNotEmpty($response->json('data.0.suggestions'));
        $this->assertFalse($response->json('data.1.busy'));
    }

    public function testTodayReturnsTheDayQueueAcrossTasks(): void
    {
        Carbon::setTestNow('2026-07-08 10:00:00');
        $this->actAsAuthorised();

        $taskA = ScheduledTask::factory()->create(['name' => 'A']);
        $taskB = ScheduledTask::factory()->create(['name' => 'B']);
        ScheduledTaskRun::factory()->create([
            'scheduled_task_id' => $taskA->id,
            'scheduled_for' => Carbon::parse('2026-07-08 08:00:00'),
            'status' => ScheduledTaskRun::STATUS_COMPLETED,
        ]);
        ScheduledTaskRun::factory()->create([
            'scheduled_task_id' => $taskB->id,
            'scheduled_for' => Carbon::parse('2026-07-08 17:30:00'),
        ]);
        ScheduledTaskRun::factory()->create([
            'scheduled_task_id' => $taskA->id,
            'scheduled_for' => Carbon::parse('2026-07-07 08:00:00'), // yesterday
            'status' => ScheduledTaskRun::STATUS_COMPLETED,
        ]);

        $response = $this->getJson('/api/settings/scheduled-task-runs/today');

        $response->assertOk();
        $this->assertCount(2, $response->json('data'));
        $this->assertEquals(['A', 'B'], collect($response->json('data'))->pluck('task_name')->toArray());
    }

    private function actAsAuthorised(): void
    {
        // getUser() seeds the global roles; the permission seed must run after them
        $this->actingAs($this->getUser());
        (new ScheduledTaskPermissionSeed())->run();
        app(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions();
    }

    private function actAsUnauthorised(): void
    {
        $this->actingAs($this->getUser(null));
    }
}
