<?php

use App\Filament\Resources\BackupScheduleResource\Pages\ListBackupSchedules;
use App\Jobs\RunBackupScheduleJob;
use App\Models\BackupSchedule;
use App\Models\OperationRun;
use App\Services\Graph\GraphClientInterface;
use App\Services\OperationRunService;
use Carbon\CarbonImmutable;
use Filament\Facades\Filament;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;

beforeEach(function () {
    $this->mock(GraphClientInterface::class, function ($mock): void {
        $mock->shouldReceive('listPolicies')->never();
        $mock->shouldReceive('getPolicy')->never();
        $mock->shouldReceive('getOrganization')->never();
        $mock->shouldReceive('applyPolicy')->never();
        $mock->shouldReceive('getServicePrincipalPermissions')->never();
        $mock->shouldReceive('request')->never();
    });
});

test('operator can run now without persisting a database notification', function () {
    Queue::fake([RunBackupScheduleJob::class]);

    [$user, $tenant] = createUserWithTenant(role: 'operator');

    $schedule = BackupSchedule::query()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Nightly',
        'is_enabled' => true,
        'timezone' => 'UTC',
        'frequency' => 'daily',
        'time_of_day' => '01:00:00',
        'days_of_week' => null,
        'policy_types' => ['deviceConfiguration'],
        'include_foundations' => true,
        'retention_keep_last' => 30,
    ]);

    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    Livewire::test(ListBackupSchedules::class)
        ->callTableAction('runNow', $schedule);

    $operationRun = OperationRun::query()
        ->where('managed_environment_id', $tenant->id)
        ->where('type', 'backup.schedule.execute')
        ->first();

    expect($operationRun)->not->toBeNull();
    expect($operationRun->user_id)->toBe($user->id);
    expect($operationRun->context)->toMatchArray([
        'backup_schedule_id' => (int) $schedule->id,
        'trigger' => 'run_now',
    ]);

    Queue::assertPushed(RunBackupScheduleJob::class, function (RunBackupScheduleJob $job) use ($schedule, $operationRun): bool {
        return $job->backupScheduleId === (int) $schedule->getKey()
            && $job->operationRun instanceof OperationRun
            && $job->operationRun->is($operationRun);
    });

    $this->assertDatabaseCount('notifications', 0);
});

test('run now is unique per click (no dedupe)', function () {
    Queue::fake([RunBackupScheduleJob::class]);

    [$user, $tenant] = createUserWithTenant(role: 'operator');

    $schedule = BackupSchedule::query()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Nightly',
        'is_enabled' => true,
        'timezone' => 'UTC',
        'frequency' => 'daily',
        'time_of_day' => '01:00:00',
        'days_of_week' => null,
        'policy_types' => ['deviceConfiguration'],
        'include_foundations' => true,
        'retention_keep_last' => 30,
    ]);

    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    Livewire::test(ListBackupSchedules::class)
        ->callTableAction('runNow', $schedule);

    Livewire::test(ListBackupSchedules::class)
        ->callTableAction('runNow', $schedule);

    $runs = OperationRun::query()
        ->where('managed_environment_id', $tenant->id)
        ->where('type', 'backup.schedule.execute')
        ->pluck('id')
        ->all();

    expect($runs)->toHaveCount(2);
    expect($runs[0])->not->toBe($runs[1]);

    Queue::assertPushed(RunBackupScheduleJob::class, 2);
    $this->assertDatabaseCount('notifications', 0);
});

test('operator can retry without persisting a database notification', function () {
    Queue::fake([RunBackupScheduleJob::class]);

    [$user, $tenant] = createUserWithTenant(role: 'operator');

    $schedule = BackupSchedule::query()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Nightly',
        'is_enabled' => true,
        'timezone' => 'UTC',
        'frequency' => 'daily',
        'time_of_day' => '01:00:00',
        'days_of_week' => null,
        'policy_types' => ['deviceConfiguration'],
        'include_foundations' => true,
        'retention_keep_last' => 30,
    ]);

    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    Livewire::test(ListBackupSchedules::class)
        ->callTableAction('retry', $schedule);

    $operationRun = OperationRun::query()
        ->where('managed_environment_id', $tenant->id)
        ->where('type', 'backup.schedule.execute')
        ->first();

    expect($operationRun)->not->toBeNull();
    expect($operationRun->user_id)->toBe($user->id);
    expect($operationRun->context)->toMatchArray([
        'backup_schedule_id' => (int) $schedule->id,
        'trigger' => 'retry',
    ]);

    Queue::assertPushed(RunBackupScheduleJob::class, function (RunBackupScheduleJob $job) use ($schedule, $operationRun): bool {
        return $job->backupScheduleId === (int) $schedule->getKey()
            && $job->operationRun instanceof OperationRun
            && $job->operationRun->is($operationRun);
    });
    $this->assertDatabaseCount('notifications', 0);
});

test('retry is unique per click (no dedupe)', function () {
    Queue::fake([RunBackupScheduleJob::class]);

    [$user, $tenant] = createUserWithTenant(role: 'operator');

    $schedule = BackupSchedule::query()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Nightly',
        'is_enabled' => true,
        'timezone' => 'UTC',
        'frequency' => 'daily',
        'time_of_day' => '01:00:00',
        'days_of_week' => null,
        'policy_types' => ['deviceConfiguration'],
        'include_foundations' => true,
        'retention_keep_last' => 30,
    ]);

    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    Livewire::test(ListBackupSchedules::class)
        ->callTableAction('retry', $schedule);

    Livewire::test(ListBackupSchedules::class)
        ->callTableAction('retry', $schedule);

    $runs = OperationRun::query()
        ->where('managed_environment_id', $tenant->id)
        ->where('type', 'backup.schedule.execute')
        ->pluck('id')
        ->all();

    expect($runs)->toHaveCount(2);
    expect($runs[0])->not->toBe($runs[1]);

    Queue::assertPushed(RunBackupScheduleJob::class, 2);
    $this->assertDatabaseCount('notifications', 0);
});

test('readonly cannot dispatch run now or retry', function () {
    [$user, $tenant] = createUserWithTenant(role: 'readonly');

    $schedule = BackupSchedule::query()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Nightly',
        'is_enabled' => true,
        'timezone' => 'UTC',
        'frequency' => 'daily',
        'time_of_day' => '01:00:00',
        'days_of_week' => null,
        'policy_types' => ['deviceConfiguration'],
        'include_foundations' => true,
        'retention_keep_last' => 30,
    ]);

    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    try {
        Livewire::test(ListBackupSchedules::class)
            ->callTableAction('runNow', $schedule);
    } catch (\Throwable) {
        // Action should be hidden/blocked for readonly users.
    }

    try {
        Livewire::test(ListBackupSchedules::class)
            ->callTableAction('retry', $schedule);
    } catch (\Throwable) {
        // Action should be hidden/blocked for readonly users.
    }

    expect(OperationRun::query()
        ->where('managed_environment_id', $tenant->id)
        ->whereIn('type', ['backup.schedule.execute'])
        ->count())
        ->toBe(0);
});

test('operator can bulk run now without persisting a database notification', function () {
    Queue::fake([RunBackupScheduleJob::class]);

    [$user, $tenant] = createUserWithTenant(role: 'operator');

    $scheduleA = BackupSchedule::query()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Nightly A',
        'is_enabled' => true,
        'timezone' => 'UTC',
        'frequency' => 'daily',
        'time_of_day' => '01:00:00',
        'days_of_week' => null,
        'policy_types' => ['deviceConfiguration'],
        'include_foundations' => true,
        'retention_keep_last' => 30,
    ]);

    $scheduleB = BackupSchedule::query()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Nightly B',
        'is_enabled' => true,
        'timezone' => 'UTC',
        'frequency' => 'daily',
        'time_of_day' => '02:00:00',
        'days_of_week' => null,
        'policy_types' => ['deviceConfiguration'],
        'include_foundations' => true,
        'retention_keep_last' => 30,
    ]);

    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    Livewire::test(ListBackupSchedules::class)
        ->callTableBulkAction('bulk_run_now', collect([$scheduleA, $scheduleB]));

    expect(OperationRun::query()
        ->where('managed_environment_id', $tenant->id)
        ->where('type', 'backup.schedule.execute')
        ->count())
        ->toBe(2);

    expect(OperationRun::query()
        ->where('managed_environment_id', $tenant->id)
        ->where('type', 'backup.schedule.execute')
        ->pluck('user_id')
        ->unique()
        ->values()
        ->all())
        ->toBe([$user->id]);

    Queue::assertPushed(RunBackupScheduleJob::class, 2);
    $this->assertDatabaseCount('notifications', 0);
});

test('operator can bulk retry without persisting a database notification', function () {
    Queue::fake([RunBackupScheduleJob::class]);

    [$user, $tenant] = createUserWithTenant(role: 'operator');

    $scheduleA = BackupSchedule::query()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Nightly A',
        'is_enabled' => true,
        'timezone' => 'UTC',
        'frequency' => 'daily',
        'time_of_day' => '01:00:00',
        'days_of_week' => null,
        'policy_types' => ['deviceConfiguration'],
        'include_foundations' => true,
        'retention_keep_last' => 30,
    ]);

    $scheduleB = BackupSchedule::query()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Nightly B',
        'is_enabled' => true,
        'timezone' => 'UTC',
        'frequency' => 'daily',
        'time_of_day' => '02:00:00',
        'days_of_week' => null,
        'policy_types' => ['deviceConfiguration'],
        'include_foundations' => true,
        'retention_keep_last' => 30,
    ]);

    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    Livewire::test(ListBackupSchedules::class)
        ->callTableBulkAction('bulk_retry', collect([$scheduleA, $scheduleB]));

    expect(OperationRun::query()
        ->where('managed_environment_id', $tenant->id)
        ->where('type', 'backup.schedule.execute')
        ->count())
        ->toBe(2);

    expect(OperationRun::query()
        ->where('managed_environment_id', $tenant->id)
        ->where('type', 'backup.schedule.execute')
        ->pluck('user_id')
        ->unique()
        ->values()
        ->all())
        ->toBe([$user->id]);

    Queue::assertPushed(RunBackupScheduleJob::class, 2);
    $this->assertDatabaseCount('notifications', 0);
});

test('operator can bulk retry even if a previous canonical run exists', function () {
    Queue::fake([RunBackupScheduleJob::class]);

    $frozenNow = CarbonImmutable::parse('2026-02-10 01:04:06', 'UTC');
    CarbonImmutable::setTestNow($frozenNow);

    try {
        [$user, $tenant] = createUserWithTenant(role: 'operator');

        $scheduleA = BackupSchedule::query()->create([
            'managed_environment_id' => $tenant->id,
            'name' => 'Nightly A',
            'is_enabled' => true,
            'timezone' => 'UTC',
            'frequency' => 'daily',
            'time_of_day' => '01:00:00',
            'days_of_week' => null,
            'policy_types' => ['deviceConfiguration'],
            'include_foundations' => true,
            'retention_keep_last' => 30,
        ]);

        $scheduleB = BackupSchedule::query()->create([
            'managed_environment_id' => $tenant->id,
            'name' => 'Nightly B',
            'is_enabled' => true,
            'timezone' => 'UTC',
            'frequency' => 'daily',
            'time_of_day' => '02:00:00',
            'days_of_week' => null,
            'policy_types' => ['deviceConfiguration'],
            'include_foundations' => true,
            'retention_keep_last' => 30,
        ]);

        /** @var OperationRunService $operationRunService */
        $operationRunService = app(OperationRunService::class);
        $existing = $operationRunService->ensureRunWithIdentity(
            tenant: $tenant,
            type: 'backup.schedule.execute',
            identityInputs: [
                'backup_schedule_id' => (int) $scheduleA->getKey(),
                'nonce' => 'existing',
            ],
            context: [
                'backup_schedule_id' => (int) $scheduleA->getKey(),
                'trigger' => 'retry',
            ],
            initiator: $user,
        );
        $operationRunService->updateRun($existing, status: 'completed', outcome: 'succeeded');

        $this->actingAs($user);
        Filament::setTenant($tenant, true);

        Livewire::test(ListBackupSchedules::class)
            ->callTableBulkAction('bulk_retry', collect([$scheduleA, $scheduleB]));

        expect(OperationRun::query()
            ->where('managed_environment_id', $tenant->id)
            ->where('type', 'backup.schedule.execute')
            ->count())
            ->toBe(3);

        Queue::assertPushed(RunBackupScheduleJob::class, 2);
    } finally {
        CarbonImmutable::setTestNow();
    }
});
