<?php

namespace App\Filament\Resources;

use App\Filament\Resources\BackupSetResource\Pages;
use App\Filament\Resources\BackupSetResource\RelationManagers\BackupItemsRelationManager;
use App\Jobs\BulkBackupSetDeleteJob;
use App\Jobs\BulkBackupSetForceDeleteJob;
use App\Jobs\BulkBackupSetRestoreJob;
use App\Models\BackupSet;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Auth\CapabilityResolver;
use App\Services\Intune\AuditLogger;
use App\Services\Intune\BackupService;
use App\Services\OperationRunService;
use App\Services\Operations\BulkSelectionIdentity;
use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\CrossResourceNavigationMatrix;
use App\Support\Navigation\RelatedContextEntry;
use App\Support\Navigation\RelatedNavigationResolver;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\OperationUxPresenter;
use App\Support\Rbac\UiEnforcement;
use BackedEnum;
use Filament\Actions;
use Filament\Actions\ActionGroup;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Infolists;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use UnitEnum;

class BackupSetResource extends Resource
{
    protected static ?string $model = BackupSet::class;

    protected static ?string $tenantOwnershipRelationshipName = 'tenant';

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

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

    public static function canViewAny(): bool
    {
        $tenant = Tenant::current();
        $user = auth()->user();

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

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

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

    public static function canCreate(): bool
    {
        $tenant = Tenant::current();
        $user = auth()->user();

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

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

        return $resolver->isMember($user, $tenant)
            && $resolver->can($user, $tenant, Capabilities::TENANT_SYNC);
    }

    public static function getEloquentQuery(): Builder
    {
        $tenant = Filament::getTenant();

        if (! $tenant instanceof Tenant) {
            return parent::getEloquentQuery()->whereRaw('1 = 0');
        }

        return parent::getEloquentQuery()->where('tenant_id', (int) $tenant->getKey());
    }

    public static function form(Schema $schema): Schema
    {
        return $schema
            ->schema([
                Forms\Components\TextInput::make('name')
                    ->label('Backup name')
                    ->default(fn () => now()->format('Y-m-d H:i:s').' backup')
                    ->required(),
            ]);
    }

    public static function makeCreateAction(): Actions\CreateAction
    {
        $action = Actions\CreateAction::make()
            ->label('Create backup set');

        UiEnforcement::forAction($action)
            ->requireCapability(Capabilities::TENANT_SYNC)
            ->apply();

        return $action;
    }

    public static function table(Table $table): Table
    {
        return $table
            ->defaultSort('created_at', 'desc')
            ->paginated(TablePaginationProfiles::resource())
            ->persistFiltersInSession()
            ->persistSearchInSession()
            ->persistSortInSession()
            ->columns([
                Tables\Columns\TextColumn::make('name')
                    ->searchable()
                    ->sortable(),
                Tables\Columns\TextColumn::make('status')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::BackupSetStatus))
                    ->color(BadgeRenderer::color(BadgeDomain::BackupSetStatus))
                    ->icon(BadgeRenderer::icon(BadgeDomain::BackupSetStatus))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::BackupSetStatus)),
                Tables\Columns\TextColumn::make('item_count')->label('Items')->numeric()->sortable(),
                Tables\Columns\TextColumn::make('created_by')->label('Created by')->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('completed_at')->label('Completed')->dateTime()->since()->sortable(),
                Tables\Columns\TextColumn::make('created_at')->label('Captured')->dateTime()->since()->sortable()->toggleable(isToggledHiddenByDefault: true),
            ])
            ->filters([
                Tables\Filters\TrashedFilter::make()
                    ->label('Archived')
                    ->placeholder('Active')
                    ->trueLabel('All')
                    ->falseLabel('Archived'),
            ])
            ->recordUrl(fn (BackupSet $record): string => static::getUrl('view', ['record' => $record]))
            ->actions([
                static::primaryRelatedAction(),
                ActionGroup::make([
                    UiEnforcement::forAction(
                        Actions\Action::make('restore')
                            ->label('Restore')
                            ->color('success')
                            ->icon('heroicon-o-arrow-uturn-left')
                            ->requiresConfirmation()
                            ->visible(fn (BackupSet $record): bool => $record->trashed())
                            ->action(function (BackupSet $record, AuditLogger $auditLogger) {
                                $tenant = Filament::getTenant();

                                $record->restore();
                                $record->items()->withTrashed()->restore();

                                if ($record->tenant) {
                                    $auditLogger->log(
                                        tenant: $record->tenant,
                                        action: 'backup.restored',
                                        resourceType: 'backup_set',
                                        resourceId: (string) $record->id,
                                        status: 'success',
                                        context: ['metadata' => ['name' => $record->name]]
                                    );
                                }

                                Notification::make()
                                    ->title('Backup set restored')
                                    ->success()
                                    ->send();
                            })
                    )
                        ->preserveVisibility()
                        ->requireCapability(Capabilities::TENANT_MANAGE)
                        ->apply(),
                    UiEnforcement::forAction(
                        Actions\Action::make('archive')
                            ->label('Archive')
                            ->color('danger')
                            ->icon('heroicon-o-archive-box-x-mark')
                            ->requiresConfirmation()
                            ->visible(fn (BackupSet $record): bool => ! $record->trashed())
                            ->action(function (BackupSet $record, AuditLogger $auditLogger) {
                                $tenant = Filament::getTenant();

                                $record->delete();

                                if ($record->tenant) {
                                    $auditLogger->log(
                                        tenant: $record->tenant,
                                        action: 'backup.deleted',
                                        resourceType: 'backup_set',
                                        resourceId: (string) $record->id,
                                        status: 'success',
                                        context: ['metadata' => ['name' => $record->name]]
                                    );
                                }

                                Notification::make()
                                    ->title('Backup set archived')
                                    ->success()
                                    ->send();
                            })
                    )
                        ->preserveVisibility()
                        ->requireCapability(Capabilities::TENANT_MANAGE)
                        ->apply(),
                    UiEnforcement::forAction(
                        Actions\Action::make('forceDelete')
                            ->label('Force delete')
                            ->color('danger')
                            ->icon('heroicon-o-trash')
                            ->requiresConfirmation()
                            ->visible(fn (BackupSet $record): bool => $record->trashed())
                            ->action(function (BackupSet $record, AuditLogger $auditLogger) {
                                $tenant = Filament::getTenant();

                                if ($record->restoreRuns()->withTrashed()->exists()) {
                                    Notification::make()
                                        ->title('Cannot force delete backup set')
                                        ->body('Backup sets referenced by restore runs cannot be removed.')
                                        ->danger()
                                        ->send();

                                    return;
                                }

                                if ($record->tenant) {
                                    $auditLogger->log(
                                        tenant: $record->tenant,
                                        action: 'backup.force_deleted',
                                        resourceType: 'backup_set',
                                        resourceId: (string) $record->id,
                                        status: 'success',
                                        context: ['metadata' => ['name' => $record->name]]
                                    );
                                }

                                $record->items()->withTrashed()->forceDelete();
                                $record->forceDelete();

                                Notification::make()
                                    ->title('Backup set permanently deleted')
                                    ->success()
                                    ->send();
                            })
                    )
                        ->preserveVisibility()
                        ->requireCapability(Capabilities::TENANT_DELETE)
                        ->apply(),
                ])->icon('heroicon-o-ellipsis-vertical'),
            ])
            ->bulkActions([
                BulkActionGroup::make([
                    UiEnforcement::forBulkAction(
                        BulkAction::make('bulk_delete')
                            ->label('Archive Backup Sets')
                            ->icon('heroicon-o-archive-box-x-mark')
                            ->color('danger')
                            ->requiresConfirmation()
                            ->hidden(function (HasTable $livewire): bool {
                                $trashedFilterState = $livewire->getTableFilterState(TrashedFilter::class) ?? [];
                                $value = $trashedFilterState['value'] ?? null;

                                $isOnlyTrashed = in_array($value, [0, '0', false], true);

                                return $isOnlyTrashed;
                            })
                            ->modalDescription('This archives backup sets (soft delete). Already archived backup sets will be skipped.')
                            ->form(function (Collection $records) {
                                if ($records->count() >= 10) {
                                    return [
                                        Forms\Components\TextInput::make('confirmation')
                                            ->label('Type DELETE to confirm')
                                            ->required()
                                            ->in(['DELETE'])
                                            ->validationMessages([
                                                'in' => 'Please type DELETE to confirm.',
                                            ]),
                                    ];
                                }

                                return [];
                            })
                            ->action(function (Collection $records) {
                                $tenant = Tenant::current();
                                $user = auth()->user();
                                $count = $records->count();
                                $ids = $records->pluck('id')->toArray();

                                if (! $tenant instanceof Tenant) {
                                    return;
                                }

                                $initiator = $user instanceof User ? $user : null;

                                /** @var BulkSelectionIdentity $selection */
                                $selection = app(BulkSelectionIdentity::class);
                                $selectionIdentity = $selection->fromIds($ids);

                                /** @var OperationRunService $runs */
                                $runs = app(OperationRunService::class);

                                $opRun = $runs->enqueueBulkOperation(
                                    tenant: $tenant,
                                    type: 'backup_set.delete',
                                    targetScope: [
                                        'entra_tenant_id' => (string) ($tenant->tenant_id ?? $tenant->external_id),
                                    ],
                                    selectionIdentity: $selectionIdentity,
                                    dispatcher: function ($operationRun) use ($tenant, $initiator, $ids): void {
                                        BulkBackupSetDeleteJob::dispatch(
                                            tenantId: (int) $tenant->getKey(),
                                            userId: (int) ($initiator?->getKey() ?? 0),
                                            backupSetIds: $ids,
                                            operationRun: $operationRun,
                                        );
                                    },
                                    initiator: $initiator,
                                    extraContext: [
                                        'backup_set_count' => $count,
                                    ],
                                    emitQueuedNotification: false,
                                );

                                OperationUxPresenter::queuedToast('backup_set.delete')
                                    ->actions([
                                        Actions\Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($opRun, $tenant)),
                                    ])
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_MANAGE)
                        ->apply(),

                    UiEnforcement::forBulkAction(
                        BulkAction::make('bulk_restore')
                            ->label('Restore Backup Sets')
                            ->icon('heroicon-o-arrow-uturn-left')
                            ->color('success')
                            ->requiresConfirmation()
                            ->hidden(function (HasTable $livewire): bool {
                                $trashedFilterState = $livewire->getTableFilterState(TrashedFilter::class) ?? [];
                                $value = $trashedFilterState['value'] ?? null;

                                $isOnlyTrashed = in_array($value, [0, '0', false], true);

                                return ! $isOnlyTrashed;
                            })
                            ->modalHeading(fn (Collection $records) => "Restore {$records->count()} backup sets?")
                            ->modalDescription('Archived backup sets will be restored back to the active list. Active backup sets will be skipped.')
                            ->action(function (Collection $records) {
                                $tenant = Tenant::current();
                                $user = auth()->user();
                                $count = $records->count();
                                $ids = $records->pluck('id')->toArray();

                                if (! $tenant instanceof Tenant) {
                                    return;
                                }

                                $initiator = $user instanceof User ? $user : null;

                                /** @var BulkSelectionIdentity $selection */
                                $selection = app(BulkSelectionIdentity::class);
                                $selectionIdentity = $selection->fromIds($ids);

                                /** @var OperationRunService $runs */
                                $runs = app(OperationRunService::class);

                                $opRun = $runs->enqueueBulkOperation(
                                    tenant: $tenant,
                                    type: 'backup_set.restore',
                                    targetScope: [
                                        'entra_tenant_id' => (string) ($tenant->tenant_id ?? $tenant->external_id),
                                    ],
                                    selectionIdentity: $selectionIdentity,
                                    dispatcher: function ($operationRun) use ($tenant, $initiator, $ids): void {
                                        BulkBackupSetRestoreJob::dispatch(
                                            tenantId: (int) $tenant->getKey(),
                                            userId: (int) ($initiator?->getKey() ?? 0),
                                            backupSetIds: $ids,
                                            operationRun: $operationRun,
                                        );
                                    },
                                    initiator: $initiator,
                                    extraContext: [
                                        'backup_set_count' => $count,
                                    ],
                                    emitQueuedNotification: false,
                                );

                                OperationUxPresenter::queuedToast('backup_set.restore')
                                    ->actions([
                                        Actions\Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($opRun, $tenant)),
                                    ])
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_MANAGE)
                        ->apply(),

                    UiEnforcement::forBulkAction(
                        BulkAction::make('bulk_force_delete')
                            ->label('Force Delete Backup Sets')
                            ->icon('heroicon-o-trash')
                            ->color('danger')
                            ->requiresConfirmation()
                            ->hidden(function (HasTable $livewire): bool {
                                $trashedFilterState = $livewire->getTableFilterState(TrashedFilter::class) ?? [];
                                $value = $trashedFilterState['value'] ?? null;

                                $isOnlyTrashed = in_array($value, [0, '0', false], true);

                                return ! $isOnlyTrashed;
                            })
                            ->modalHeading(fn (Collection $records) => "Force delete {$records->count()} backup sets?")
                            ->modalDescription('This is permanent. Only archived backup sets will be permanently deleted; active backup sets will be skipped.')
                            ->form(function (Collection $records) {
                                if ($records->count() >= 10) {
                                    return [
                                        Forms\Components\TextInput::make('confirmation')
                                            ->label('Type DELETE to confirm')
                                            ->required()
                                            ->in(['DELETE'])
                                            ->validationMessages([
                                                'in' => 'Please type DELETE to confirm.',
                                            ]),
                                    ];
                                }

                                return [];
                            })
                            ->action(function (Collection $records) {
                                $tenant = Tenant::current();
                                $user = auth()->user();
                                $count = $records->count();
                                $ids = $records->pluck('id')->toArray();

                                if (! $tenant instanceof Tenant) {
                                    return;
                                }

                                $initiator = $user instanceof User ? $user : null;

                                /** @var BulkSelectionIdentity $selection */
                                $selection = app(BulkSelectionIdentity::class);
                                $selectionIdentity = $selection->fromIds($ids);

                                /** @var OperationRunService $runs */
                                $runs = app(OperationRunService::class);

                                $opRun = $runs->enqueueBulkOperation(
                                    tenant: $tenant,
                                    type: 'backup_set.force_delete',
                                    targetScope: [
                                        'entra_tenant_id' => (string) ($tenant->tenant_id ?? $tenant->external_id),
                                    ],
                                    selectionIdentity: $selectionIdentity,
                                    dispatcher: function ($operationRun) use ($tenant, $initiator, $ids): void {
                                        BulkBackupSetForceDeleteJob::dispatch(
                                            tenantId: (int) $tenant->getKey(),
                                            userId: (int) ($initiator?->getKey() ?? 0),
                                            backupSetIds: $ids,
                                            operationRun: $operationRun,
                                        );
                                    },
                                    initiator: $initiator,
                                    extraContext: [
                                        'backup_set_count' => $count,
                                    ],
                                    emitQueuedNotification: false,
                                );

                                OperationUxPresenter::queuedToast('backup_set.force_delete')
                                    ->actions([
                                        Actions\Action::make('view_run')
                                            ->label('View run')
                                            ->url(OperationRunLinks::view($opRun, $tenant)),
                                    ])
                                    ->send();
                            })
                            ->deselectRecordsAfterCompletion(),
                    )
                        ->requireCapability(Capabilities::TENANT_DELETE)
                        ->apply(),
                ])->label('More'),
            ])
            ->emptyStateHeading('No backup sets')
            ->emptyStateDescription('Create a backup set to start protecting your configurations.')
            ->emptyStateIcon('heroicon-o-archive-box')
            ->emptyStateActions([
                static::makeCreateAction(),
            ]);
    }

    public static function infolist(Schema $schema): Schema
    {
        return $schema
            ->schema([
                Infolists\Components\TextEntry::make('name'),
                Infolists\Components\TextEntry::make('status')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::BackupSetStatus))
                    ->color(BadgeRenderer::color(BadgeDomain::BackupSetStatus))
                    ->icon(BadgeRenderer::icon(BadgeDomain::BackupSetStatus))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::BackupSetStatus)),
                Infolists\Components\TextEntry::make('item_count')->label('Items'),
                Infolists\Components\TextEntry::make('created_by')->label('Created by'),
                Infolists\Components\TextEntry::make('completed_at')->dateTime(),
                Infolists\Components\TextEntry::make('metadata')
                    ->label('Metadata')
                    ->formatStateUsing(fn ($state) => json_encode($state ?? [], JSON_PRETTY_PRINT))
                    ->copyable()
                    ->copyMessage('Metadata copied'),
                Section::make('Related context')
                    ->schema([
                        Infolists\Components\ViewEntry::make('related_context')
                            ->label('')
                            ->view('filament.infolists.entries.related-context')
                            ->state(fn (BackupSet $record): array => static::relatedContextEntries($record))
                            ->columnSpanFull(),
                    ])
                    ->columnSpanFull(),
            ]);
    }

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

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListBackupSets::route('/'),
            'create' => Pages\CreateBackupSet::route('/create'),
            'view' => Pages\ViewBackupSet::route('/{record}'),
        ];
    }

    /**
     * @return array{label:?string,category:?string,restore:?string,risk:?string}|array<string,mixed>
     */
    private static function typeMeta(?string $type): array
    {
        if ($type === null) {
            return [];
        }

        $types = array_merge(
            config('tenantpilot.supported_policy_types', []),
            config('tenantpilot.foundation_types', [])
        );

        return collect($types)
            ->firstWhere('type', $type) ?? [];
    }

    /**
     * @return list<array{
     *     key: string,
     *     label: string,
     *     value: string,
     *     secondaryValue: ?string,
     *     targetUrl: ?string,
     *     targetKind: string,
     *     availability: string,
     *     unavailableReason: ?string,
     *     contextBadge: ?string,
     *     priority: int,
     *     actionLabel: string
     * }>
     */
    public static function relatedContextEntries(BackupSet $record): array
    {
        return app(RelatedNavigationResolver::class)
            ->detailEntries(CrossResourceNavigationMatrix::SOURCE_BACKUP_SET, $record);
    }

    private static function primaryRelatedAction(): Actions\Action
    {
        return Actions\Action::make('primary_drill_down')
            ->label(fn (BackupSet $record): string => static::primaryRelatedEntry($record)?->actionLabel ?? 'Open related record')
            ->url(fn (BackupSet $record): ?string => static::primaryRelatedEntry($record)?->targetUrl)
            ->hidden(fn (BackupSet $record): bool => ! (static::primaryRelatedEntry($record)?->isAvailable() ?? false))
            ->color('gray');
    }

    private static function primaryRelatedEntry(BackupSet $record): ?RelatedContextEntry
    {
        return app(RelatedNavigationResolver::class)
            ->primaryListAction(CrossResourceNavigationMatrix::SOURCE_BACKUP_SET, $record);
    }

    /**
     * Create a backup set via the domain service instead of direct model mass-assignment.
     */
    public static function createBackupSet(array $data): BackupSet
    {
        /** @var Tenant $tenant */
        $tenant = Tenant::current();

        /** @var BackupService $service */
        $service = app(BackupService::class);

        return $service->createBackupSet(
            tenant: $tenant,
            policyIds: $data['policy_ids'] ?? [],
            name: $data['name'] ?? null,
            actorEmail: auth()->user()?->email,
            actorName: auth()->user()?->name,
            includeAssignments: $data['include_assignments'] ?? false,
            includeScopeTags: $data['include_scope_tags'] ?? false,
        );
    }
}
