<?php

namespace App\Filament\Resources;

use App\Exceptions\InvalidPolicyTypeException;
use App\Filament\Concerns\InteractsWithTenantOwnedRecords;
use App\Filament\Concerns\ResolvesPanelTenantContext;
use App\Filament\Concerns\WorkspaceScopedEnvironmentRoutes;
use App\Filament\Resources\BackupScheduleResource\Pages;
use App\Filament\Resources\BackupScheduleResource\RelationManagers\BackupScheduleOperationRunsRelationManager;
use App\Jobs\RunBackupScheduleJob;
use App\Models\BackupSchedule;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Rules\SupportedPolicyTypesRule;
use App\Services\Auth\CapabilityResolver;
use App\Services\BackupScheduling\PolicyTypeResolver;
use App\Services\BackupScheduling\ScheduleTimeService;
use App\Services\Intune\AuditLogger;
use App\Services\OperationRunService;
use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Badges\TagBadgeDomain;
use App\Support\Badges\TagBadgeRenderer;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\NavigationScope;
use App\Support\OperationRunLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunType;
use App\Support\OpsUx\OperationUxPresenter;
use App\Support\OpsUx\OpsUxBrowserEvents;
use App\Support\Rbac\UiEnforcement;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceType;
use BackedEnum;
use DateTimeZone;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
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\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use UnitEnum;

class BackupScheduleResource extends Resource
{
    use InteractsWithTenantOwnedRecords;
    use ResolvesPanelTenantContext;
    use WorkspaceScopedEnvironmentRoutes;

    protected static ?string $model = BackupSchedule::class;

    protected static ?string $tenantOwnershipRelationshipName = 'tenant';

    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-clock';

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

    public static function shouldRegisterNavigation(): bool
    {
        return NavigationScope::shouldRegisterEnvironmentNavigation()
            && parent::shouldRegisterNavigation();
    }

    public static function canViewAny(): bool
    {
        $tenant = static::resolveTenantContextForCurrentPanel();

        $user = auth()->user();

        if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User) {
            return false;
        }

        /** @var CapabilityResolver $resolver */
        $resolver = app(CapabilityResolver::class);

        return $resolver->can($user, $tenant, Capabilities::TENANT_VIEW);
    }

    public static function canView(Model $record): bool
    {
        $tenant = static::resolveTenantContextForCurrentPanel();

        $user = auth()->user();

        if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User) {
            return false;
        }

        /** @var CapabilityResolver $resolver */
        $resolver = app(CapabilityResolver::class);

        if (! $resolver->can($user, $tenant, Capabilities::TENANT_VIEW)) {
            return false;
        }

        if ($record instanceof BackupSchedule) {
            return (int) $record->managed_environment_id === (int) $tenant->getKey();
        }

        return true;
    }

    public static function canCreate(): bool
    {
        $tenant = static::resolveTenantContextForCurrentPanel();

        $user = auth()->user();

        if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User) {
            return false;
        }

        /** @var CapabilityResolver $resolver */
        $resolver = app(CapabilityResolver::class);

        return $resolver->can($user, $tenant, Capabilities::TENANT_BACKUP_SCHEDULES_MANAGE);
    }

    public static function canEdit(Model $record): bool
    {
        $tenant = static::resolveTenantContextForCurrentPanel();

        $user = auth()->user();

        if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User) {
            return false;
        }

        /** @var CapabilityResolver $resolver */
        $resolver = app(CapabilityResolver::class);

        return $resolver->can($user, $tenant, Capabilities::TENANT_BACKUP_SCHEDULES_MANAGE);
    }

    public static function canDelete(Model $record): bool
    {
        $tenant = static::resolveTenantContextForCurrentPanel();

        $user = auth()->user();

        if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User) {
            return false;
        }

        /** @var CapabilityResolver $resolver */
        $resolver = app(CapabilityResolver::class);

        return $resolver->can($user, $tenant, Capabilities::TENANT_BACKUP_SCHEDULES_MANAGE);
    }

    public static function canDeleteAny(): bool
    {
        $tenant = static::resolveTenantContextForCurrentPanel();

        $user = auth()->user();

        if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User) {
            return false;
        }

        /** @var CapabilityResolver $resolver */
        $resolver = app(CapabilityResolver::class);

        return $resolver->can($user, $tenant, Capabilities::TENANT_BACKUP_SCHEDULES_MANAGE);
    }

    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndEdit, ActionSurfaceType::CrudListFirstResource)
            ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions include capability-gated create.')
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
            ->satisfy(ActionSurfaceSlot::ListRowMoreMenu, 'Row-level secondary actions are grouped under "More" in workflow-first, destructive-last order.')
            ->satisfy(ActionSurfaceSlot::ListBulkMoreGroup, 'Bulk actions are grouped under "More" in workflow-first, destructive-last order.')
            ->satisfy(ActionSurfaceSlot::ListEmptyState, 'List page defines a capability-gated empty-state create CTA.')
            ->satisfy(ActionSurfaceSlot::DetailHeader, 'Edit page provides save/cancel controls.');
    }

    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 makeCreateAction(): CreateAction
    {
        return CreateAction::make()
            ->label('New backup schedule')
            ->disabled(fn (): bool => ! static::canCreate())
            ->tooltip(fn (): ?string => static::canCreate()
                ? null
                : 'You do not have permission to create backup schedules.');
    }

    public static function table(Table $table): Table
    {
        return $table
            ->defaultSort('next_run_at', 'asc')
            ->paginated(TablePaginationProfiles::resource())
            ->persistFiltersInSession()
            ->persistSearchInSession()
            ->persistSortInSession()
            ->recordUrl(fn (BackupSchedule $record): ?string => static::canEdit($record)
                ? static::getUrl('edit', ['record' => $record])
                : null)
            ->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()
                    ->sortable()
                    ->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')
                    ->toggleable(isToggledHiddenByDefault: true),

                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')
                    ->toggleable(isToggledHiddenByDefault: true),

                TextColumn::make('last_run_status')
                    ->label('Last run status')
                    ->badge()
                    ->formatStateUsing(function (?string $state): string {
                        $outcome = static::scheduleStatusToOutcome($state);

                        if (! filled($outcome)) {
                            return '—';
                        }

                        return BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, $outcome)->label;
                    })
                    ->color(function (?string $state): string {
                        $outcome = static::scheduleStatusToOutcome($state);

                        if (! filled($outcome)) {
                            return 'gray';
                        }

                        return BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, $outcome)->color;
                    })
                    ->icon(function (?string $state): ?string {
                        $outcome = static::scheduleStatusToOutcome($state);

                        if (! filled($outcome)) {
                            return null;
                        }

                        return BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, $outcome)->icon;
                    })
                    ->iconColor(function (?string $state): string {
                        $outcome = static::scheduleStatusToOutcome($state);

                        if (! filled($outcome)) {
                            return 'gray';
                        }

                        $spec = BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, $outcome);

                        return $spec->iconColor ?? $spec->color;
                    })
                    ->toggleable(isToggledHiddenByDefault: true),

                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');
                        }
                    })
                    ->description(fn (BackupSchedule $record): ?string => static::scheduleFollowUpDescription($record))
                    ->sortable(),
            ])
            ->filters([
                TrashedFilter::make()
                    ->label('Archived')
                    ->placeholder('Active')
                    ->trueLabel('All')
                    ->falseLabel('Archived'),
                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([
                    UiEnforcement::forAction(
                        Action::make('runNow')
                            ->label('Run now')
                            ->icon('heroicon-o-play')
                            ->color('success')
                            ->visible(fn (BackupSchedule $record): bool => ! $record->trashed())
                            ->action(function (BackupSchedule $record, HasTable $livewire): void {
                                $tenant = static::resolveTenantContextForCurrentPanel();

                                if (! $tenant instanceof ManagedEnvironment) {
                                    Notification::make()
                                        ->title('No environment selected')
                                        ->danger()
                                        ->send();

                                    return;
                                }

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

                                /** @var OperationRunService $operationRunService */
                                $operationRunService = app(OperationRunService::class);
                                $nonce = (string) Str::uuid();
                                $operationRun = $operationRunService->ensureRunWithIdentity(
                                    tenant: $tenant,
                                    type: OperationRunType::BackupScheduleExecute->value,
                                    identityInputs: [
                                        'backup_schedule_id' => (int) $record->getKey(),
                                        'nonce' => $nonce,
                                    ],
                                    context: [
                                        'backup_schedule_id' => (int) $record->getKey(),
                                        'trigger' => 'run_now',
                                    ],
                                    initiator: $userModel,
                                );

                                app(AuditLogger::class)->log(
                                    tenant: $tenant,
                                    action: 'backup_schedule.run_dispatched_manual',
                                    resourceType: 'operation_run',
                                    resourceId: (string) $operationRun->getKey(),
                                    status: 'success',
                                    context: [
                                        'metadata' => [
                                            'backup_schedule_id' => $record->id,
                                            'operation_run_id' => $operationRun->getKey(),
                                            'trigger' => 'run_now',
                                        ],
                                    ],
                                );

                                $operationRunService->dispatchOrFail($operationRun, function () use ($record, $operationRun): void {
                                    Bus::dispatch(new RunBackupScheduleJob(operationRun: $operationRun, backupScheduleId: (int) $record->getKey()));
                                });

                                OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
                                OperationUxPresenter::queuedToast((string) $operationRun->type)
                                    ->actions([
                                        Action::make('view_run')
                                            ->label(OperationRunLinks::openLabel())
                                            ->url(OperationRunLinks::view($operationRun, $tenant)),
                                    ])
                                    ->send();
                            })
                    )
                        ->preserveVisibility()
                        ->requireCapability(Capabilities::TENANT_BACKUP_SCHEDULES_RUN)
                        ->apply(),
                    UiEnforcement::forAction(
                        Action::make('retry')
                            ->label('Retry')
                            ->icon('heroicon-o-arrow-path')
                            ->color('warning')
                            ->visible(fn (BackupSchedule $record): bool => ! $record->trashed())
                            ->action(function (BackupSchedule $record, HasTable $livewire): void {
                                $tenant = static::resolveTenantContextForCurrentPanel();

                                if (! $tenant instanceof ManagedEnvironment) {
                                    Notification::make()
                                        ->title('No environment selected')
                                        ->danger()
                                        ->send();

                                    return;
                                }

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

                                /** @var OperationRunService $operationRunService */
                                $operationRunService = app(OperationRunService::class);
                                $nonce = (string) Str::uuid();
                                $operationRun = $operationRunService->ensureRunWithIdentity(
                                    tenant: $tenant,
                                    type: OperationRunType::BackupScheduleExecute->value,
                                    identityInputs: [
                                        'backup_schedule_id' => (int) $record->getKey(),
                                        'nonce' => $nonce,
                                    ],
                                    context: [
                                        'backup_schedule_id' => (int) $record->getKey(),
                                        'trigger' => 'retry',
                                    ],
                                    initiator: $userModel,
                                );

                                app(AuditLogger::class)->log(
                                    tenant: $tenant,
                                    action: 'backup_schedule.run_dispatched_manual',
                                    resourceType: 'operation_run',
                                    resourceId: (string) $operationRun->getKey(),
                                    status: 'success',
                                    context: [
                                        'metadata' => [
                                            'backup_schedule_id' => $record->id,
                                            'operation_run_id' => $operationRun->getKey(),
                                            'trigger' => 'retry',
                                        ],
                                    ],
                                );

                                $operationRunService->dispatchOrFail($operationRun, function () use ($record, $operationRun): void {
                                    Bus::dispatch(new RunBackupScheduleJob(operationRun: $operationRun, backupScheduleId: (int) $record->getKey()));
                                });

                                OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
                                OperationUxPresenter::queuedToast((string) $operationRun->type)
                                    ->actions([
                                        Action::make('view_run')
                                            ->label(OperationRunLinks::openLabel())
                                            ->url(OperationRunLinks::view($operationRun, $tenant)),
                                    ])
                                    ->send();
                            })
                    )
                        ->preserveVisibility()
                        ->requireCapability(Capabilities::TENANT_BACKUP_SCHEDULES_RUN)
                        ->apply(),
                    UiEnforcement::forAction(
                        Action::make('restore')
                            ->label('Restore')
                            ->icon('heroicon-o-arrow-uturn-left')
                            ->color('success')
                            ->visible(fn (BackupSchedule $record): bool => $record->trashed())
                            ->action(function (BackupSchedule $record, AuditLogger $auditLogger): void {
                                $record = static::resolveProtectedScheduleRecordOrFail($record);

                                Gate::authorize('restore', $record);

                                if (! $record->trashed()) {
                                    return;
                                }

                                $record->restore();

                                if ($record->tenant instanceof ManagedEnvironment) {
                                    $auditLogger->log(
                                        tenant: $record->tenant,
                                        action: 'backup_schedule.restored',
                                        resourceType: 'backup_schedule',
                                        resourceId: (string) $record->getKey(),
                                        status: 'success',
                                        context: [
                                            'metadata' => [
                                                'backup_schedule_id' => $record->getKey(),
                                                'backup_schedule_name' => $record->name,
                                            ],
                                        ],
                                    );
                                }

                                Notification::make()
                                    ->title('Backup schedule restored')
                                    ->success()
                                    ->send();
                            })
                    )
                        ->preserveVisibility()
                        ->requireCapability(Capabilities::TENANT_BACKUP_SCHEDULES_MANAGE)
                        ->apply(),
                    UiEnforcement::forAction(
                        Action::make('archive')
                            ->label('Archive')
                            ->icon('heroicon-o-archive-box-x-mark')
                            ->color('danger')
                            ->requiresConfirmation()
                            ->visible(fn (BackupSchedule $record): bool => ! $record->trashed())
                            ->action(function (BackupSchedule $record, AuditLogger $auditLogger): void {
                                $record = static::resolveProtectedScheduleRecordOrFail($record);

                                Gate::authorize('delete', $record);

                                if ($record->trashed()) {
                                    return;
                                }

                                $record->delete();

                                if ($record->tenant instanceof ManagedEnvironment) {
                                    $auditLogger->log(
                                        tenant: $record->tenant,
                                        action: 'backup_schedule.archived',
                                        resourceType: 'backup_schedule',
                                        resourceId: (string) $record->getKey(),
                                        status: 'success',
                                        context: [
                                            'metadata' => [
                                                'backup_schedule_id' => $record->getKey(),
                                                'backup_schedule_name' => $record->name,
                                            ],
                                        ],
                                    );
                                }

                                Notification::make()
                                    ->title('Backup schedule archived')
                                    ->success()
                                    ->send();
                            })
                    )
                        ->preserveVisibility()
                        ->requireCapability(Capabilities::TENANT_BACKUP_SCHEDULES_MANAGE)
                        ->destructive()
                        ->apply(),
                    UiEnforcement::forAction(
                        Action::make('forceDelete')
                            ->label('Force delete')
                            ->icon('heroicon-o-trash')
                            ->color('danger')
                            ->requiresConfirmation()
                            ->visible(fn (BackupSchedule $record): bool => $record->trashed())
                            ->action(function (BackupSchedule $record, AuditLogger $auditLogger): void {
                                $record = static::resolveProtectedScheduleRecordOrFail($record);

                                Gate::authorize('forceDelete', $record);

                                if (! $record->trashed()) {
                                    return;
                                }

                                if ($record->operationRuns()->exists()) {
                                    Notification::make()
                                        ->title('Cannot force delete backup schedule')
                                        ->body('Backup schedules referenced by historical runs cannot be removed.')
                                        ->danger()
                                        ->send();

                                    return;
                                }

                                if ($record->tenant instanceof ManagedEnvironment) {
                                    $auditLogger->log(
                                        tenant: $record->tenant,
                                        action: 'backup_schedule.force_deleted',
                                        resourceType: 'backup_schedule',
                                        resourceId: (string) $record->getKey(),
                                        status: 'success',
                                        context: [
                                            'metadata' => [
                                                'backup_schedule_id' => $record->getKey(),
                                                'backup_schedule_name' => $record->name,
                                            ],
                                        ],
                                    );
                                }

                                $record->forceDelete();

                                Notification::make()
                                    ->title('Backup schedule permanently deleted')
                                    ->success()
                                    ->send();
                            })
                    )
                        ->preserveVisibility()
                        ->requireCapability(Capabilities::TENANT_DELETE)
                        ->destructive()
                        ->apply(),
                ])
                    ->label('More')
                    ->icon('heroicon-o-ellipsis-vertical')
                    ->color('gray'),
            ])
            ->bulkActions([
                BulkActionGroup::make([
                    UiEnforcement::forBulkAction(
                        BulkAction::make('bulk_run_now')
                            ->label('Run now')
                            ->icon('heroicon-o-play')
                            ->color('success')
                            ->action(function (Collection $records, HasTable $livewire): void {
                                $tenant = static::resolveTenantContextForCurrentPanel();

                                if (! $tenant instanceof ManagedEnvironment) {
                                    Notification::make()
                                        ->title('No environment selected')
                                        ->danger()
                                        ->send();

                                    return;
                                }

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

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

                                $bulkRun = null;

                                $createdOperationRunIds = [];

                                /** @var BackupSchedule $record */
                                foreach ($records as $record) {
                                    $nonce = (string) Str::uuid();
                                    $operationRun = $operationRunService->ensureRunWithIdentity(
                                        tenant: $tenant,
                                        type: OperationRunType::BackupScheduleExecute->value,
                                        identityInputs: [
                                            'backup_schedule_id' => (int) $record->getKey(),
                                            'nonce' => $nonce,
                                        ],
                                        context: [
                                            'backup_schedule_id' => (int) $record->getKey(),
                                            'trigger' => 'bulk_run_now',
                                        ],
                                        initiator: $user,
                                    );

                                    $createdOperationRunIds[] = (int) $operationRun->getKey();

                                    app(AuditLogger::class)->log(
                                        tenant: $tenant,
                                        action: 'backup_schedule.run_dispatched_manual',
                                        resourceType: 'operation_run',
                                        resourceId: (string) $operationRun->getKey(),
                                        status: 'success',
                                        context: [
                                            'metadata' => [
                                                'backup_schedule_id' => $record->id,
                                                'operation_run_id' => $operationRun->getKey(),
                                                'trigger' => 'bulk_run_now',
                                            ],
                                        ],
                                    );

                                    $operationRunService->dispatchOrFail($operationRun, function () use ($record, $operationRun): void {
                                        Bus::dispatch(new RunBackupScheduleJob(operationRun: $operationRun, backupScheduleId: (int) $record->getKey()));
                                    }, emitQueuedNotification: false);
                                }

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

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

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

                                $notification->send();

                                if (count($createdOperationRunIds) > 0) {
                                    OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
                                }
                            })
                    )
                        ->requireCapability(Capabilities::TENANT_BACKUP_SCHEDULES_RUN)
                        ->apply(),
                    UiEnforcement::forBulkAction(
                        BulkAction::make('bulk_retry')
                            ->label('Retry')
                            ->icon('heroicon-o-arrow-path')
                            ->color('warning')
                            ->action(function (Collection $records, HasTable $livewire): void {
                                $tenant = static::resolveTenantContextForCurrentPanel();

                                if (! $tenant instanceof ManagedEnvironment) {
                                    Notification::make()
                                        ->title('No environment selected')
                                        ->danger()
                                        ->send();

                                    return;
                                }

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

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

                                $bulkRun = null;

                                $createdOperationRunIds = [];

                                /** @var BackupSchedule $record */
                                foreach ($records as $record) {
                                    $nonce = (string) Str::uuid();
                                    $operationRun = $operationRunService->ensureRunWithIdentity(
                                        tenant: $tenant,
                                        type: OperationRunType::BackupScheduleExecute->value,
                                        identityInputs: [
                                            'backup_schedule_id' => (int) $record->getKey(),
                                            'nonce' => $nonce,
                                        ],
                                        context: [
                                            'backup_schedule_id' => (int) $record->getKey(),
                                            'trigger' => 'bulk_retry',
                                        ],
                                        initiator: $user,
                                    );

                                    $createdOperationRunIds[] = (int) $operationRun->getKey();

                                    app(AuditLogger::class)->log(
                                        tenant: $tenant,
                                        action: 'backup_schedule.run_dispatched_manual',
                                        resourceType: 'operation_run',
                                        resourceId: (string) $operationRun->getKey(),
                                        status: 'success',
                                        context: [
                                            'metadata' => [
                                                'backup_schedule_id' => $record->id,
                                                'operation_run_id' => $operationRun->getKey(),
                                                'trigger' => 'bulk_retry',
                                            ],
                                        ],
                                    );

                                    $operationRunService->dispatchOrFail($operationRun, function () use ($record, $operationRun): void {
                                        Bus::dispatch(new RunBackupScheduleJob(operationRun: $operationRun, backupScheduleId: (int) $record->getKey()));
                                    }, emitQueuedNotification: false);
                                }

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

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

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

                                $notification->send();

                                if (count($createdOperationRunIds) > 0) {
                                    OpsUxBrowserEvents::dispatchRunEnqueued($livewire);
                                }
                            })
                    )
                        ->requireCapability(Capabilities::TENANT_BACKUP_SCHEDULES_RUN)
                        ->apply(),
                ])->label('More'),
            ])
            ->emptyStateHeading('No schedules configured')
            ->emptyStateDescription('Set up automated backups.')
            ->emptyStateIcon('heroicon-o-clock')
            ->emptyStateActions([
                static::makeCreateAction(),
            ]);
    }

    public static function getEloquentQuery(): Builder
    {
        return static::getTenantOwnedEloquentQuery()
            ->orderByDesc('is_enabled')
            ->orderBy('next_run_at');
    }

    public static function getRecordRouteBindingEloquentQuery(): Builder
    {
        return static::scopeTenantOwnedQuery(parent::getEloquentQuery()->withTrashed())
            ->orderByDesc('is_enabled')
            ->orderBy('next_run_at');
    }

    public static function resolveScopedRecordOrFail(int|string $key): Model
    {
        return static::resolveTenantOwnedRecordOrFail($key, parent::getEloquentQuery()->withTrashed());
    }

    protected static function resolveProtectedScheduleRecordOrFail(BackupSchedule|int|string $record): BackupSchedule
    {
        $resolvedRecord = static::resolveScopedRecordOrFail($record instanceof Model ? $record->getKey() : $record);

        if (! $resolvedRecord instanceof BackupSchedule) {
            abort(404);
        }

        return $resolvedRecord;
    }

    public static function getRelations(): array
    {
        return [
            BackupScheduleOperationRunsRelationManager::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['managed_environment_id'] = static::resolveTenantContextForCurrentPanelOrFail()->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 scheduleStatusToOutcome(?string $status): ?string
    {
        return match (strtolower(trim((string) $status))) {
            'running' => OperationRunOutcome::Pending->value,
            'success' => OperationRunOutcome::Succeeded->value,
            'partial' => OperationRunOutcome::PartiallySucceeded->value,
            'skipped' => OperationRunOutcome::Blocked->value,
            'failed', 'canceled' => OperationRunOutcome::Failed->value,
            default => null,
        };
    }

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

    protected static function scheduleFollowUpDescription(BackupSchedule $record): ?string
    {
        if (! $record->is_enabled || $record->trashed()) {
            return null;
        }

        $graceCutoff = now('UTC')->subMinutes(max(1, (int) config('tenantpilot.backup_health.schedule_overdue_grace_minutes', 30)));
        $lastRunStatus = strtolower(trim((string) $record->last_run_status));
        $isOverdue = $record->next_run_at?->lessThan($graceCutoff) ?? false;
        $neverSuccessful = $record->last_run_at === null
            && ($isOverdue || ($record->created_at?->lessThan($graceCutoff) ?? false));

        if ($neverSuccessful) {
            return 'No successful run has been recorded yet.';
        }

        if ($isOverdue) {
            return 'This schedule looks overdue.';
        }

        if (in_array($lastRunStatus, ['failed', 'partial', 'skipped', 'canceled'], true)) {
            return 'The last run needs follow-up.';
        }

        return null;
    }
}
