<?php

namespace App\Filament\Resources;

use App\Exceptions\InvalidPolicyTypeException;
use App\Filament\Resources\BackupScheduleResource\Pages;
use App\Filament\Resources\BackupScheduleResource\RelationManagers\BackupScheduleRunsRelationManager;
use App\Jobs\RunBackupScheduleJob;
use App\Models\BackupSchedule;
use App\Models\BackupScheduleRun;
use App\Models\Tenant;
use App\Models\User;
use App\Rules\SupportedPolicyTypesRule;
use App\Services\BackupScheduling\PolicyTypeResolver;
use App\Services\BackupScheduling\ScheduleTimeService;
use App\Services\Intune\AuditLogger;
use App\Services\OperationRunService;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Badges\TagBadgeDomain;
use App\Support\Badges\TagBadgeRenderer;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\OperationUxPresenter;
use App\Support\OpsUx\OpsUxBrowserEvents;
use App\Support\TenantRole;
use BackedEnum;
use Carbon\CarbonImmutable;
use DateTimeZone;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use UnitEnum;

class BackupScheduleResource extends Resource
{
    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-clock';

    protected static string|UnitEnum|null $navigationGroup = 'Backups & Restore';

    protected static function currentTenantRole(): ?TenantRole
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            return null;
        }

        return $user->tenantRole(Tenant::current());
    }

    public static function canViewAny(): bool
    {
        return static::currentTenantRole() !== null;
    }

    public static function canView(Model $record): bool
    {
        return static::currentTenantRole() !== null;
    }

    public static function canCreate(): bool
    {
        return static::currentTenantRole()?->canManageBackupSchedules() ?? false;
    }

    public static function canEdit(Model $record): bool
    {
        return static::currentTenantRole()?->canManageBackupSchedules() ?? false;
    }

    public static function canDelete(Model $record): bool
    {
        return static::currentTenantRole()?->canManageBackupSchedules() ?? false;
    }

    public static function canDeleteAny(): bool
    {
        return static::currentTenantRole()?->canManageBackupSchedules() ?? false;
    }

    public static function form(Schema $schema): Schema
    {
        return $schema
            ->schema([
                TextInput::make('name')
                    ->label('Schedule Name')
                    ->required()
                    ->maxLength(255),

                Toggle::make('is_enabled')
                    ->label('Enabled')
                    ->default(true),

                Select::make('timezone')
                    ->label('Timezone')
                    ->options(static::timezoneOptions())
                    ->searchable()
                    ->default('UTC')
                    ->required(),

                Select::make('frequency')
                    ->label('Frequency')
                    ->options([
                        'daily' => 'Daily',
                        'weekly' => 'Weekly',
                    ])
                    ->default('daily')
                    ->required()
                    ->reactive(),

                TextInput::make('time_of_day')
                    ->label('Time of day')
                    ->type('time')
                    ->required()
                    ->extraInputAttributes(['step' => 60]),

                CheckboxList::make('days_of_week')
                    ->label('Days of the week')
                    ->options(static::dayOfWeekOptions())
                    ->columns(2)
                    ->visible(fn (Get $get): bool => $get('frequency') === 'weekly')
                    ->required(fn (Get $get): bool => $get('frequency') === 'weekly')
                    ->rules(['array', 'min:1']),

                CheckboxList::make('policy_types')
                    ->label('Policy types')
                    ->options(static::policyTypeOptions())
                    ->columns(2)
                    ->required()
                    ->helperText('Select the Microsoft Graph policy types that should be included in each run.')
                    ->rules([
                        'array',
                        'min:1',
                        new SupportedPolicyTypesRule,
                    ])
                    ->columnSpanFull(),

                Toggle::make('include_foundations')
                    ->label('Include foundation types')
                    ->default(true),

                TextInput::make('retention_keep_last')
                    ->label('Retention (keep last N Backup Sets)')
                    ->type('number')
                    ->default(30)
                    ->minValue(1)
                    ->required(),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->defaultSort('next_run_at', 'asc')
            ->columns([
                TextColumn::make('is_enabled')
                    ->label('Enabled')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::BooleanEnabled))
                    ->color(BadgeRenderer::color(BadgeDomain::BooleanEnabled))
                    ->icon(BadgeRenderer::icon(BadgeDomain::BooleanEnabled))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::BooleanEnabled))
                    ->alignCenter(),

                TextColumn::make('name')
                    ->searchable()
                    ->label('Schedule'),

                TextColumn::make('frequency')
                    ->label('Frequency')
                    ->badge()
                    ->formatStateUsing(TagBadgeRenderer::label(TagBadgeDomain::BackupScheduleFrequency))
                    ->color(TagBadgeRenderer::color(TagBadgeDomain::BackupScheduleFrequency)),

                TextColumn::make('time_of_day')
                    ->label('Time')
                    ->formatStateUsing(fn (?string $state): ?string => $state ? trim($state) : null),

                TextColumn::make('timezone')
                    ->label('Timezone'),

                TextColumn::make('policy_types')
                    ->label('Policy types')
                    ->getStateUsing(fn (BackupSchedule $record): string => static::policyTypesPreviewLabel($record))
                    ->tooltip(fn (BackupSchedule $record): string => static::policyTypesFullLabel($record)),

                TextColumn::make('retention_keep_last')
                    ->label('Retention')
                    ->suffix(' sets'),

                TextColumn::make('last_run_status')
                    ->label('Last run status')
                    ->badge()
                    ->formatStateUsing(function (?string $state): string {
                        if (! filled($state)) {
                            return '—';
                        }

                        return BadgeRenderer::spec(BadgeDomain::BackupScheduleRunStatus, $state)->label;
                    })
                    ->color(function (?string $state): string {
                        if (! filled($state)) {
                            return 'gray';
                        }

                        return BadgeRenderer::spec(BadgeDomain::BackupScheduleRunStatus, $state)->color;
                    })
                    ->icon(function (?string $state): ?string {
                        if (! filled($state)) {
                            return null;
                        }

                        return BadgeRenderer::spec(BadgeDomain::BackupScheduleRunStatus, $state)->icon;
                    })
                    ->iconColor(function (?string $state): string {
                        if (! filled($state)) {
                            return 'gray';
                        }

                        $spec = BadgeRenderer::spec(BadgeDomain::BackupScheduleRunStatus, $state);

                        return $spec->iconColor ?? $spec->color;
                    }),

                TextColumn::make('last_run_at')
                    ->label('Last run')
                    ->dateTime()
                    ->sortable(),

                TextColumn::make('next_run_at')
                    ->label('Next run')
                    ->getStateUsing(function (BackupSchedule $record): ?string {
                        $nextRun = $record->next_run_at;

                        if (! $nextRun) {
                            return null;
                        }

                        $timezone = $record->timezone ?: 'UTC';

                        try {
                            return $nextRun->setTimezone($timezone)->format('M j, Y H:i:s');
                        } catch (\Throwable) {
                            return $nextRun->format('M j, Y H:i:s');
                        }
                    })
                    ->sortable(),
            ])
            ->filters([
                SelectFilter::make('enabled_state')
                    ->label('Enabled')
                    ->options([
                        'enabled' => 'Enabled',
                        'disabled' => 'Disabled',
                    ])
                    ->query(function (Builder $query, array $data): void {
                        $value = $data['value'] ?? null;

                        if (blank($value)) {
                            return;
                        }

                        if ($value === 'enabled') {
                            $query->where('is_enabled', true);

                            return;
                        }

                        if ($value === 'disabled') {
                            $query->where('is_enabled', false);
                        }
                    }),
            ])
            ->actions([
                ActionGroup::make([
                    Action::make('runNow')
                        ->label('Run now')
                        ->icon('heroicon-o-play')
                        ->color('success')
                        ->visible(fn (): bool => static::currentTenantRole()?->canRunBackupSchedules() ?? false)
                        ->action(function (BackupSchedule $record, HasTable $livewire): void {
                            abort_unless(static::currentTenantRole()?->canRunBackupSchedules() ?? false, 403);

                            $tenant = Tenant::current();
                            $user = auth()->user();
                            $userId = auth()->id();
                            $userModel = $user instanceof User ? $user : ($userId ? User::query()->find($userId) : null);

                            /** @var OperationRunService $operationRunService */
                            $operationRunService = app(OperationRunService::class);
                            $operationRun = $operationRunService->ensureRun(
                                tenant: $tenant,
                                type: 'backup_schedule.run_now',
                                inputs: [
                                    'backup_schedule_id' => (int) $record->getKey(),
                                ],
                                initiator: $userModel
                            );

                            if (! $operationRun->wasRecentlyCreated && in_array($operationRun->status, ['queued', 'running'], true)) {
                                Notification::make()
                                    ->title('Run already queued')
                                    ->body('This schedule already has a queued or running backup.')
                                    ->warning()
                                    ->actions([
                                        Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($operationRun, $tenant)),
                                    ])
                                    ->send();

                                return;
                            }

                            $scheduledFor = CarbonImmutable::now('UTC')->startOfMinute();
                            $run = null;

                            for ($i = 0; $i < 5; $i++) {
                                try {
                                    $run = BackupScheduleRun::create([
                                        'backup_schedule_id' => $record->id,
                                        'tenant_id' => $tenant->getKey(),
                                        'user_id' => $userId,
                                        'scheduled_for' => $scheduledFor->toDateTimeString(),
                                        'status' => BackupScheduleRun::STATUS_RUNNING,
                                        'summary' => null,
                                    ]);
                                    break;
                                } catch (UniqueConstraintViolationException) {
                                    $scheduledFor = $scheduledFor->addMinute();
                                }
                            }

                            if (! $run instanceof BackupScheduleRun) {
                                Notification::make()
                                    ->title('Run already queued')
                                    ->body('Please wait a moment and try again.')
                                    ->warning()
                                    ->actions([
                                        Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($operationRun, $tenant)),
                                    ])
                                    ->send();

                                $operationRunService->updateRun(
                                    $operationRun,
                                    status: 'completed',
                                    outcome: 'failed',
                                    summaryCounts: [
                                        'backup_schedule_id' => (int) $record->getKey(),
                                    ],
                                    failures: [
                                        [
                                            'code' => 'SCHEDULE_CONFLICT',
                                            'message' => 'Unable to queue a unique backup schedule run.',
                                        ],
                                    ],
                                );

                                return;
                            }

                            $operationRun->update([
                                'context' => array_merge($operationRun->context ?? [], [
                                    'backup_schedule_id' => (int) $record->getKey(),
                                    'backup_schedule_run_id' => (int) $run->getKey(),
                                ]),
                            ]);

                            app(AuditLogger::class)->log(
                                tenant: $tenant,
                                action: 'backup_schedule.run_dispatched_manual',
                                resourceType: 'backup_schedule_run',
                                resourceId: (string) $run->id,
                                status: 'success',
                                context: [
                                    'metadata' => [
                                        'backup_schedule_id' => $record->id,
                                        'backup_schedule_run_id' => $run->id,
                                        'scheduled_for' => $scheduledFor->toDateTimeString(),
                                        'trigger' => 'run_now',
                                    ],
                                ],
                            );

                            $operationRunService->dispatchOrFail($operationRun, function () use ($run, $operationRun): void {
                                Bus::dispatch(new RunBackupScheduleJob($run->id, $operationRun));
                            });

                            OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
                            OperationUxPresenter::queuedToast((string) $operationRun->type)
                                ->actions([
                                    Action::make('view_run')
                                        ->label('View run')
                                        ->url(OperationRunLinks::view($operationRun, $tenant)),
                                ])
                                ->send();
                        }),
                    Action::make('retry')
                        ->label('Retry')
                        ->icon('heroicon-o-arrow-path')
                        ->color('warning')
                        ->visible(fn (): bool => static::currentTenantRole()?->canRunBackupSchedules() ?? false)
                        ->action(function (BackupSchedule $record, HasTable $livewire): void {
                            abort_unless(static::currentTenantRole()?->canRunBackupSchedules() ?? false, 403);

                            $tenant = Tenant::current();
                            $user = auth()->user();
                            $userId = auth()->id();
                            $userModel = $user instanceof User ? $user : ($userId ? User::query()->find($userId) : null);

                            /** @var OperationRunService $operationRunService */
                            $operationRunService = app(OperationRunService::class);
                            $operationRun = $operationRunService->ensureRun(
                                tenant: $tenant,
                                type: 'backup_schedule.retry',
                                inputs: [
                                    'backup_schedule_id' => (int) $record->getKey(),
                                ],
                                initiator: $userModel
                            );

                            if (! $operationRun->wasRecentlyCreated && in_array($operationRun->status, ['queued', 'running'], true)) {
                                Notification::make()
                                    ->title('Retry already queued')
                                    ->body('This schedule already has a queued or running retry.')
                                    ->warning()
                                    ->actions([
                                        Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($operationRun, $tenant)),
                                    ])
                                    ->send();

                                return;
                            }

                            $scheduledFor = CarbonImmutable::now('UTC')->startOfMinute();
                            $run = null;

                            for ($i = 0; $i < 5; $i++) {
                                try {
                                    $run = BackupScheduleRun::create([
                                        'backup_schedule_id' => $record->id,
                                        'tenant_id' => $tenant->getKey(),
                                        'user_id' => $userId,
                                        'scheduled_for' => $scheduledFor->toDateTimeString(),
                                        'status' => BackupScheduleRun::STATUS_RUNNING,
                                        'summary' => null,
                                    ]);
                                    break;
                                } catch (UniqueConstraintViolationException) {
                                    $scheduledFor = $scheduledFor->addMinute();
                                }
                            }

                            if (! $run instanceof BackupScheduleRun) {
                                Notification::make()
                                    ->title('Retry already queued')
                                    ->body('Please wait a moment and try again.')
                                    ->warning()
                                    ->actions([
                                        Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($operationRun, $tenant)),
                                    ])
                                    ->send();

                                $operationRunService->updateRun(
                                    $operationRun,
                                    status: 'completed',
                                    outcome: 'failed',
                                    summaryCounts: [
                                        'backup_schedule_id' => (int) $record->getKey(),
                                    ],
                                    failures: [
                                        [
                                            'code' => 'SCHEDULE_CONFLICT',
                                            'message' => 'Unable to queue a unique backup schedule retry run.',
                                        ],
                                    ],
                                );

                                return;
                            }

                            $operationRun->update([
                                'context' => array_merge($operationRun->context ?? [], [
                                    'backup_schedule_id' => (int) $record->getKey(),
                                    'backup_schedule_run_id' => (int) $run->getKey(),
                                ]),
                            ]);

                            app(AuditLogger::class)->log(
                                tenant: $tenant,
                                action: 'backup_schedule.run_dispatched_manual',
                                resourceType: 'backup_schedule_run',
                                resourceId: (string) $run->id,
                                status: 'success',
                                context: [
                                    'metadata' => [
                                        'backup_schedule_id' => $record->id,
                                        'backup_schedule_run_id' => $run->id,
                                        'scheduled_for' => $scheduledFor->toDateTimeString(),
                                        'trigger' => 'retry',
                                    ],
                                ],
                            );

                            $operationRunService->dispatchOrFail($operationRun, function () use ($run, $operationRun): void {
                                Bus::dispatch(new RunBackupScheduleJob($run->id, $operationRun));
                            });

                            OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
                            OperationUxPresenter::queuedToast((string) $operationRun->type)
                                ->actions([
                                    Action::make('view_run')
                                        ->label('View run')
                                        ->url(OperationRunLinks::view($operationRun, $tenant)),
                                ])
                                ->send();
                        }),
                    EditAction::make()
                        ->visible(fn (): bool => static::currentTenantRole()?->canManageBackupSchedules() ?? false),
                    DeleteAction::make()
                        ->visible(fn (): bool => static::currentTenantRole()?->canManageBackupSchedules() ?? false),
                ])->icon('heroicon-o-ellipsis-vertical'),
            ])
            ->bulkActions([
                BulkActionGroup::make([
                    BulkAction::make('bulk_run_now')
                        ->label('Run now')
                        ->icon('heroicon-o-play')
                        ->color('success')
                        ->visible(fn (): bool => static::currentTenantRole()?->canRunBackupSchedules() ?? false)
                        ->action(function (Collection $records, HasTable $livewire): void {
                            abort_unless(static::currentTenantRole()?->canRunBackupSchedules() ?? false, 403);

                            if ($records->isEmpty()) {
                                return;
                            }

                            $tenant = Tenant::current();
                            $userId = auth()->id();
                            $user = $userId ? User::query()->find($userId) : null;
                            /** @var OperationRunService $operationRunService */
                            $operationRunService = app(OperationRunService::class);

                            $bulkRun = null;

                            $createdRunIds = [];

                            /** @var BackupSchedule $record */
                            foreach ($records as $record) {
                                $operationRun = $operationRunService->ensureRun(
                                    tenant: $tenant,
                                    type: 'backup_schedule.run_now',
                                    inputs: [
                                        'backup_schedule_id' => (int) $record->getKey(),
                                    ],
                                    initiator: $user
                                );

                                if (! $operationRun->wasRecentlyCreated && in_array($operationRun->status, ['queued', 'running'], true)) {
                                    continue;
                                }

                                $scheduledFor = CarbonImmutable::now('UTC')->startOfMinute();
                                $run = null;

                                for ($i = 0; $i < 5; $i++) {
                                    try {
                                        $run = BackupScheduleRun::create([
                                            'backup_schedule_id' => $record->id,
                                            'tenant_id' => $tenant->getKey(),
                                            'user_id' => $userId,
                                            'scheduled_for' => $scheduledFor->toDateTimeString(),
                                            'status' => BackupScheduleRun::STATUS_RUNNING,
                                            'summary' => null,
                                        ]);
                                        break;
                                    } catch (UniqueConstraintViolationException) {
                                        $scheduledFor = $scheduledFor->addMinute();
                                    }
                                }

                                if (! $run instanceof BackupScheduleRun) {
                                    $operationRunService->updateRun(
                                        $operationRun,
                                        status: 'completed',
                                        outcome: 'failed',
                                        summaryCounts: [
                                            'backup_schedule_id' => (int) $record->getKey(),
                                        ],
                                        failures: [
                                            [
                                                'code' => 'SCHEDULE_CONFLICT',
                                                'message' => 'Unable to queue a unique backup schedule run.',
                                            ],
                                        ],
                                    );

                                    continue;
                                }

                                $createdRunIds[] = (int) $run->id;

                                $operationRun->update([
                                    'context' => array_merge($operationRun->context ?? [], [
                                        'backup_schedule_id' => (int) $record->getKey(),
                                        'backup_schedule_run_id' => (int) $run->getKey(),
                                    ]),
                                ]);

                                app(AuditLogger::class)->log(
                                    tenant: $tenant,
                                    action: 'backup_schedule.run_dispatched_manual',
                                    resourceType: 'backup_schedule_run',
                                    resourceId: (string) $run->id,
                                    status: 'success',
                                    context: [
                                        'metadata' => [
                                            'backup_schedule_id' => $record->id,
                                            'backup_schedule_run_id' => $run->id,
                                            'scheduled_for' => $scheduledFor->toDateTimeString(),
                                            'trigger' => 'bulk_run_now',
                                        ],
                                    ],
                                );

                                $operationRunService->dispatchOrFail($operationRun, function () use ($run, $operationRun): void {
                                    Bus::dispatch(new RunBackupScheduleJob($run->id, $operationRun));
                                }, emitQueuedNotification: false);
                            }

                            $notification = Notification::make()
                                ->title('Runs dispatched')
                                ->body(sprintf('Queued %d run(s).', count($createdRunIds)));

                            if (count($createdRunIds) === 0) {
                                $notification->warning();
                            } else {
                                $notification->success();
                            }

                            if ($user instanceof User) {
                                $notification->actions([
                                    Action::make('view_runs')
                                        ->label('View in Operations')
                                        ->url(OperationRunLinks::index($tenant)),
                                ])->sendToDatabase($user);
                            }

                            $notification->send();

                            if (count($createdRunIds) > 0) {
                                OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
                            }
                        }),
                    BulkAction::make('bulk_retry')
                        ->label('Retry')
                        ->icon('heroicon-o-arrow-path')
                        ->color('warning')
                        ->visible(fn (): bool => static::currentTenantRole()?->canRunBackupSchedules() ?? false)
                        ->action(function (Collection $records, HasTable $livewire): void {
                            abort_unless(static::currentTenantRole()?->canRunBackupSchedules() ?? false, 403);

                            if ($records->isEmpty()) {
                                return;
                            }

                            $tenant = Tenant::current();
                            $userId = auth()->id();
                            $user = $userId ? User::query()->find($userId) : null;
                            /** @var OperationRunService $operationRunService */
                            $operationRunService = app(OperationRunService::class);

                            $bulkRun = null;

                            $createdRunIds = [];

                            /** @var BackupSchedule $record */
                            foreach ($records as $record) {
                                $operationRun = $operationRunService->ensureRun(
                                    tenant: $tenant,
                                    type: 'backup_schedule.retry',
                                    inputs: [
                                        'backup_schedule_id' => (int) $record->getKey(),
                                    ],
                                    initiator: $user
                                );

                                if (! $operationRun->wasRecentlyCreated && in_array($operationRun->status, ['queued', 'running'], true)) {
                                    continue;
                                }

                                $scheduledFor = CarbonImmutable::now('UTC')->startOfMinute();
                                $run = null;

                                for ($i = 0; $i < 5; $i++) {
                                    try {
                                        $run = BackupScheduleRun::create([
                                            'backup_schedule_id' => $record->id,
                                            'tenant_id' => $tenant->getKey(),
                                            'user_id' => $userId,
                                            'scheduled_for' => $scheduledFor->toDateTimeString(),
                                            'status' => BackupScheduleRun::STATUS_RUNNING,
                                            'summary' => null,
                                        ]);
                                        break;
                                    } catch (UniqueConstraintViolationException) {
                                        $scheduledFor = $scheduledFor->addMinute();
                                    }
                                }

                                if (! $run instanceof BackupScheduleRun) {
                                    $operationRunService->updateRun(
                                        $operationRun,
                                        status: 'completed',
                                        outcome: 'failed',
                                        summaryCounts: [
                                            'backup_schedule_id' => (int) $record->getKey(),
                                        ],
                                        failures: [
                                            [
                                                'code' => 'SCHEDULE_CONFLICT',
                                                'message' => 'Unable to queue a unique backup schedule retry run.',
                                            ],
                                        ],
                                    );

                                    continue;
                                }

                                $createdRunIds[] = (int) $run->id;

                                $operationRun->update([
                                    'context' => array_merge($operationRun->context ?? [], [
                                        'backup_schedule_id' => (int) $record->getKey(),
                                        'backup_schedule_run_id' => (int) $run->getKey(),
                                    ]),
                                ]);

                                app(AuditLogger::class)->log(
                                    tenant: $tenant,
                                    action: 'backup_schedule.run_dispatched_manual',
                                    resourceType: 'backup_schedule_run',
                                    resourceId: (string) $run->id,
                                    status: 'success',
                                    context: [
                                        'metadata' => [
                                            'backup_schedule_id' => $record->id,
                                            'backup_schedule_run_id' => $run->id,
                                            'scheduled_for' => $scheduledFor->toDateTimeString(),
                                            'trigger' => 'bulk_retry',
                                        ],
                                    ],
                                );

                                $operationRunService->dispatchOrFail($operationRun, function () use ($run, $operationRun): void {
                                    Bus::dispatch(new RunBackupScheduleJob($run->id, $operationRun));
                                }, emitQueuedNotification: false);
                            }

                            $notification = Notification::make()
                                ->title('Retries dispatched')
                                ->body(sprintf('Queued %d run(s).', count($createdRunIds)));

                            if (count($createdRunIds) === 0) {
                                $notification->warning();
                            } else {
                                $notification->success();
                            }

                            if ($user instanceof User) {
                                $notification->actions([
                                    Action::make('view_runs')
                                        ->label('View in Operations')
                                        ->url(OperationRunLinks::index($tenant)),
                                ])->sendToDatabase($user);
                            }

                            $notification->send();

                            if (count($createdRunIds) > 0) {
                                OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
                            }
                        }),
                    DeleteBulkAction::make('bulk_delete')
                        ->visible(fn (): bool => static::currentTenantRole()?->canManageBackupSchedules() ?? false),
                ]),
            ]);
    }

    public static function getEloquentQuery(): Builder
    {
        $tenantId = Tenant::current()->getKey();

        return parent::getEloquentQuery()
            ->where('tenant_id', $tenantId)
            ->orderByDesc('is_enabled')
            ->orderBy('next_run_at');
    }

    public static function getRelations(): array
    {
        return [
            BackupScheduleRunsRelationManager::class,
        ];
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListBackupSchedules::route('/'),
            'create' => Pages\CreateBackupSchedule::route('/create'),
            'edit' => Pages\EditBackupSchedule::route('/{record}/edit'),
        ];
    }

    public static function policyTypesFullLabel(BackupSchedule $record): string
    {
        $labels = static::policyTypesLabels($record);

        return $labels === [] ? 'None' : implode(', ', $labels);
    }

    public static function policyTypesPreviewLabel(BackupSchedule $record): string
    {
        $labels = static::policyTypesLabels($record);

        if ($labels === []) {
            return 'None';
        }

        $preview = array_slice($labels, 0, 2);
        $remaining = count($labels) - count($preview);

        $label = implode(', ', $preview);

        if ($remaining > 0) {
            $label .= sprintf(' +%d more', $remaining);
        }

        return $label;
    }

    /**
     * @return array<int, string>
     */
    private static function policyTypesLabels(BackupSchedule $record): array
    {
        $state = $record->policy_types;

        if (is_string($state)) {
            $decoded = json_decode($state, true);

            if (is_array($decoded)) {
                $state = $decoded;
            }
        }

        if ($state instanceof \Illuminate\Contracts\Support\Arrayable) {
            $state = $state->toArray();
        }

        if (blank($state) || (! is_array($state))) {
            return [];
        }

        $types = array_is_list($state)
            ? $state
            : array_keys(array_filter($state));

        $types = array_values(array_filter($types, fn (mixed $type): bool => is_string($type) && $type !== ''));

        if ($types === []) {
            return [];
        }

        $labelMap = collect(config('tenantpilot.supported_policy_types', []))
            ->mapWithKeys(fn (array $policy): array => [
                (string) ($policy['type'] ?? '') => (string) ($policy['label'] ?? Str::headline((string) ($policy['type'] ?? ''))),
            ])
            ->filter(fn (string $label, string $type): bool => $type !== '')
            ->all();

        return array_map(
            fn (string $type): string => $labelMap[$type] ?? Str::headline($type),
            $types,
        );
    }

    public static function ensurePolicyTypes(array $data): array
    {
        $types = array_values((array) ($data['policy_types'] ?? []));

        try {
            app(PolicyTypeResolver::class)->ensureSupported($types);
        } catch (InvalidPolicyTypeException $exception) {
            throw ValidationException::withMessages([
                'policy_types' => [sprintf('Unknown policy types: %s.', implode(', ', $exception->unknownPolicyTypes))],
            ]);
        }

        $data['policy_types'] = $types;

        return $data;
    }

    public static function assignTenant(array $data): array
    {
        $data['tenant_id'] = Tenant::current()->getKey();

        return $data;
    }

    public static function hydrateNextRun(array $data): array
    {
        if (! empty($data['time_of_day'])) {
            $data['time_of_day'] = static::normalizeTimeOfDay($data['time_of_day']);
        }

        $schedule = new BackupSchedule;
        $schedule->forceFill([
            'frequency' => $data['frequency'] ?? 'daily',
            'time_of_day' => $data['time_of_day'] ?? '00:00:00',
            'timezone' => $data['timezone'] ?? 'UTC',
            'days_of_week' => (array) ($data['days_of_week'] ?? []),
        ]);

        $nextRun = app(ScheduleTimeService::class)->nextRunFor($schedule);

        $data['next_run_at'] = $nextRun?->toDateTimeString();

        return $data;
    }

    public static function normalizeTimeOfDay(string $time): string
    {
        if (preg_match('/^\d{2}:\d{2}$/', $time)) {
            return $time.':00';
        }

        return $time;
    }

    protected static function timezoneOptions(): array
    {
        $zones = DateTimeZone::listIdentifiers();

        sort($zones);

        return array_combine($zones, $zones);
    }

    protected static function policyTypeOptions(): array
    {
        return static::policyTypeLabelMap();
    }

    protected static function policyTypeLabels(array $types): array
    {
        $map = static::policyTypeLabelMap();

        return array_map(fn (string $type): string => $map[$type] ?? Str::headline($type), $types);
    }

    protected static function policyTypeLabelMap(): array
    {
        return collect(config('tenantpilot.supported_policy_types', []))
            ->mapWithKeys(fn (array $policy) => [
                $policy['type'] => $policy['label'] ?? Str::headline($policy['type']),
            ])
            ->all();
    }

    protected static function dayOfWeekOptions(): array
    {
        return [
            1 => 'Monday',
            2 => 'Tuesday',
            3 => 'Wednesday',
            4 => 'Thursday',
            5 => 'Friday',
            6 => 'Saturday',
            7 => 'Sunday',
        ];
    }
}
