<?php

namespace App\Filament\Resources\BackupSetResource\RelationManagers;

use App\Filament\Resources\PolicyResource;
use App\Filament\Resources\PolicyVersionResource;
use App\Jobs\RemovePoliciesFromBackupSetJob;
use App\Models\BackupItem;
use App\Models\Tenant;
use App\Models\User;
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\FilterOptionCatalog;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Inventory\InventoryPolicyTypeMeta;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\OperationUxPresenter;
use App\Support\OpsUx\OpsUxBrowserEvents;
use App\Support\Rbac\UiEnforcement;
use Filament\Actions;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;

class BackupItemsRelationManager extends RelationManager
{
    protected static string $relationship = 'items';

    protected $listeners = [
        'backup-set-policy-picker:close' => 'closeAddPoliciesModal',
    ];

    public function closeAddPoliciesModal(): void
    {
        $this->unmountAction();
    }

    public function table(Table $table): Table
    {
        $refreshTable = Actions\Action::make('refreshTable')
            ->label('Refresh')
            ->icon('heroicon-o-arrow-path')
            ->action(function (): void {
                $this->resetTable();
            });

        $addPolicies = Actions\Action::make('addPolicies')
            ->label('Add Policies')
            ->icon('heroicon-o-plus')
            ->tooltip('You do not have permission to add policies.')
            ->modalHeading('Add Policies')
            ->modalSubmitAction(false)
            ->modalCancelActionLabel('Close')
            ->modalContent(function (): View {
                $backupSet = $this->getOwnerRecord();

                return view('filament.modals.backup-set-policy-picker', [
                    'backupSetId' => $backupSet->getKey(),
                ]);
            });

        UiEnforcement::forAction($addPolicies)
            ->requireCapability(Capabilities::TENANT_SYNC)
            ->tooltip('You do not have permission to add policies.')
            ->apply();

        $removeItem = Actions\Action::make('remove')
            ->label('Remove')
            ->color('danger')
            ->icon('heroicon-o-x-mark')
            ->requiresConfirmation()
            ->action(function (BackupItem $record): void {
                $backupSet = $this->getOwnerRecord();

                $user = auth()->user();
                if (! $user instanceof User) {
                    abort(403);
                }

                $tenant = $backupSet->tenant ?? Tenant::current();
                if (! $tenant instanceof Tenant) {
                    abort(404);
                }

                if ((int) $tenant->getKey() !== (int) $backupSet->tenant_id) {
                    abort(404);
                }

                $backupItemIds = [(int) $record->getKey()];

                /** @var OperationRunService $opService */
                $opService = app(OperationRunService::class);
                $opRun = $opService->ensureRun(
                    tenant: $tenant,
                    type: 'backup_set.remove_policies',
                    inputs: [
                        'backup_set_id' => (int) $backupSet->getKey(),
                        'backup_item_ids' => $backupItemIds,
                    ],
                    initiator: $user,
                );

                if (! $opRun->wasRecentlyCreated && in_array($opRun->status, ['queued', 'running'], true)) {
                    OpsUxBrowserEvents::dispatchRunEnqueued($this);

                    OperationUxPresenter::alreadyQueuedToast((string) $opRun->type)
                        ->actions([
                            Actions\Action::make('view_run')
                                ->label('View run')
                                ->url(OperationRunLinks::view($opRun, $tenant)),
                        ])
                        ->send();

                    return;
                }

                $opService->dispatchOrFail($opRun, function () use ($backupSet, $backupItemIds, $user, $opRun): void {
                    RemovePoliciesFromBackupSetJob::dispatch(
                        backupSetId: (int) $backupSet->getKey(),
                        backupItemIds: $backupItemIds,
                        initiatorUserId: (int) $user->getKey(),
                        operationRun: $opRun,
                    );
                });

                OpsUxBrowserEvents::dispatchRunEnqueued($this);
                OperationUxPresenter::queuedToast((string) $opRun->type)
                    ->actions([
                        Actions\Action::make('view_run')
                            ->label('View run')
                            ->url(OperationRunLinks::view($opRun, $tenant)),
                    ])
                    ->send();
            });

        UiEnforcement::forAction($removeItem)
            ->requireCapability(Capabilities::TENANT_SYNC)
            ->tooltip('You do not have permission to remove policies.')
            ->apply();

        $bulkRemove = Actions\BulkAction::make('bulk_remove')
            ->label('Remove selected')
            ->icon('heroicon-o-x-mark')
            ->color('danger')
            ->requiresConfirmation()
            ->deselectRecordsAfterCompletion()
            ->action(function (Collection $records): void {
                if ($records->isEmpty()) {
                    return;
                }

                $backupSet = $this->getOwnerRecord();

                $user = auth()->user();
                if (! $user instanceof User) {
                    abort(403);
                }

                $tenant = $backupSet->tenant ?? Tenant::current();
                if (! $tenant instanceof Tenant) {
                    abort(404);
                }

                if ((int) $tenant->getKey() !== (int) $backupSet->tenant_id) {
                    abort(404);
                }

                $backupItemIds = $records
                    ->pluck('id')
                    ->map(fn (mixed $value): int => (int) $value)
                    ->filter(fn (int $value): bool => $value > 0)
                    ->unique()
                    ->sort()
                    ->values()
                    ->all();

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

                /** @var OperationRunService $opService */
                $opService = app(OperationRunService::class);
                $opRun = $opService->ensureRun(
                    tenant: $tenant,
                    type: 'backup_set.remove_policies',
                    inputs: [
                        'backup_set_id' => (int) $backupSet->getKey(),
                        'backup_item_ids' => $backupItemIds,
                    ],
                    initiator: $user,
                );

                if (! $opRun->wasRecentlyCreated && in_array($opRun->status, ['queued', 'running'], true)) {
                    OpsUxBrowserEvents::dispatchRunEnqueued($this);

                    OperationUxPresenter::alreadyQueuedToast((string) $opRun->type)
                        ->actions([
                            Actions\Action::make('view_run')
                                ->label('View run')
                                ->url(OperationRunLinks::view($opRun, $tenant)),
                        ])
                        ->send();

                    return;
                }

                $opService->dispatchOrFail($opRun, function () use ($backupSet, $backupItemIds, $user, $opRun): void {
                    RemovePoliciesFromBackupSetJob::dispatch(
                        backupSetId: (int) $backupSet->getKey(),
                        backupItemIds: $backupItemIds,
                        initiatorUserId: (int) $user->getKey(),
                        operationRun: $opRun,
                    );
                });

                OpsUxBrowserEvents::dispatchRunEnqueued($this);
                OperationUxPresenter::queuedToast((string) $opRun->type)
                    ->actions([
                        Actions\Action::make('view_run')
                            ->label('View run')
                            ->url(OperationRunLinks::view($opRun, $tenant)),
                    ])
                    ->send();
            });

        UiEnforcement::forBulkAction($bulkRemove)
            ->requireCapability(Capabilities::TENANT_SYNC)
            ->tooltip('You do not have permission to remove policies.')
            ->apply();

        return $table
            ->modifyQueryUsing(fn (Builder $query) => $query->with(['policy', 'policyVersion', 'policyVersion.policy']))
            ->defaultSort('policy.display_name')
            ->paginated(TablePaginationProfiles::relationManager())
            ->persistFiltersInSession()
            ->persistSearchInSession()
            ->persistSortInSession()
            ->columns([
                Tables\Columns\TextColumn::make('policy.display_name')
                    ->label('Item')
                    ->sortable()
                    ->searchable()
                    ->getStateUsing(fn (BackupItem $record) => $record->resolvedDisplayName()),
                Tables\Columns\TextColumn::make('policyVersion.version_number')
                    ->label('Version')
                    ->badge()
                    ->default('—')
                    ->getStateUsing(fn (BackupItem $record): ?int => $record->policyVersion?->version_number),
                Tables\Columns\TextColumn::make('policy_type')
                    ->label('Type')
                    ->badge()
                    ->formatStateUsing(TagBadgeRenderer::label(TagBadgeDomain::PolicyType))
                    ->color(TagBadgeRenderer::color(TagBadgeDomain::PolicyType)),
                Tables\Columns\TextColumn::make('restore_mode')
                    ->label('Restore')
                    ->badge()
                    ->state(fn (BackupItem $record) => static::typeMeta($record->policy_type)['restore'] ?? 'enabled')
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::PolicyRestoreMode))
                    ->color(BadgeRenderer::color(BadgeDomain::PolicyRestoreMode))
                    ->icon(BadgeRenderer::icon(BadgeDomain::PolicyRestoreMode))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::PolicyRestoreMode)),
                Tables\Columns\TextColumn::make('risk')
                    ->label('Risk')
                    ->badge()
                    ->state(fn (BackupItem $record) => static::typeMeta($record->policy_type)['risk'] ?? 'n/a')
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::PolicyRisk))
                    ->color(BadgeRenderer::color(BadgeDomain::PolicyRisk))
                    ->icon(BadgeRenderer::icon(BadgeDomain::PolicyRisk))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::PolicyRisk)),
                Tables\Columns\TextColumn::make('policy_identifier')
                    ->label('Policy ID')
                    ->copyable()
                    ->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('platform')
                    ->badge()
                    ->formatStateUsing(TagBadgeRenderer::label(TagBadgeDomain::Platform))
                    ->color(TagBadgeRenderer::color(TagBadgeDomain::Platform)),
                Tables\Columns\TextColumn::make('assignments')
                    ->label('Assignments')
                    ->badge()
                    ->color('info')
                    ->getStateUsing(function (BackupItem $record): string {
                        $assignments = $record->policyVersion?->assignments ?? $record->assignments;

                        if (is_array($assignments)) {
                            return (string) count($assignments);
                        }

                        $assignmentsFetched = $record->policyVersion?->metadata['assignments_fetched']
                            ?? $record->metadata['assignments_fetched']
                            ?? false;

                        return $assignmentsFetched ? '0' : '—';
                    }),
                Tables\Columns\TextColumn::make('scope_tags')
                    ->label('Scope Tags')
                    ->default('—')
                    ->getStateUsing(function (BackupItem $record): array {
                        $tags = $record->policyVersion?->scope_tags['names']
                            ?? $record->metadata['scope_tag_names']
                            ?? [];

                        return is_array($tags) ? $tags : [];
                    })
                    ->formatStateUsing(function ($state): string {
                        if (is_array($state)) {
                            return $state === [] ? '—' : implode(', ', $state);
                        }

                        if (is_string($state) && $state !== '') {
                            return $state;
                        }

                        return '—';
                    })
                    ->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('captured_at')->dateTime()->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('created_at')->since()->toggleable(isToggledHiddenByDefault: true),
            ])
            ->filters([
                SelectFilter::make('policy_type')
                    ->label('Type')
                    ->options(FilterOptionCatalog::policyTypes())
                    ->searchable(),
                SelectFilter::make('restore_mode')
                    ->label('Restore')
                    ->options(static::restoreModeOptions())
                    ->query(fn (Builder $query, array $data): Builder => static::applyRestoreModeFilter($query, $data['value'] ?? null)),
                SelectFilter::make('platform')
                    ->options(FilterOptionCatalog::platforms())
                    ->searchable(),
            ])
            ->headerActions([
                $refreshTable,
                $addPolicies,
            ])
            ->actions([
                Actions\ActionGroup::make([
                    Actions\ViewAction::make()
                        ->label(fn (BackupItem $record): string => $record->policy_version_id ? 'View version' : 'View policy')
                        ->url(function (BackupItem $record): ?string {
                            $tenant = $this->getOwnerRecord()->tenant ?? \App\Models\Tenant::current();

                            if ($record->policy_version_id) {
                                return PolicyVersionResource::getUrl('view', ['record' => $record->policy_version_id], tenant: $tenant);
                            }

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

                            return PolicyResource::getUrl('view', ['record' => $record->policy_id], tenant: $tenant);
                        })
                        ->hidden(fn (BackupItem $record) => ! $record->policy_version_id && ! $record->policy_id)
                        ->openUrlInNewTab(true),
                    $removeItem,
                ])
                    ->label('More')
                    ->icon('heroicon-o-ellipsis-vertical')
                    ->color('gray'),
            ])
            ->bulkActions([
                Actions\BulkActionGroup::make([
                    $bulkRemove,
                ])->label('More'),
            ])
            ->emptyStateHeading('No policies in this backup set')
            ->emptyStateDescription('Add policies to capture versions and assignments inside this backup set.')
            ->emptyStateActions([
                $addPolicies->name('addPoliciesEmpty'),
            ]);
    }

    /**
     * @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 array<string, string>
     */
    private static function restoreModeOptions(): array
    {
        return collect(InventoryPolicyTypeMeta::all())
            ->pluck('restore')
            ->filter(fn (mixed $value): bool => is_string($value) && trim($value) !== '')
            ->map(fn (string $value): string => trim($value))
            ->unique()
            ->sort()
            ->mapWithKeys(fn (string $value): array => [
                $value => BadgeRenderer::spec(BadgeDomain::PolicyRestoreMode, $value)->label,
            ])
            ->all();
    }

    private static function applyRestoreModeFilter(Builder $query, mixed $value): Builder
    {
        if (! is_string($value) || trim($value) === '') {
            return $query;
        }

        $types = collect(InventoryPolicyTypeMeta::all())
            ->filter(fn (array $meta): bool => ($meta['restore'] ?? null) === $value)
            ->pluck('type')
            ->filter(fn (mixed $type): bool => is_string($type) && trim($type) !== '')
            ->map(fn (string $type): string => trim($type))
            ->values()
            ->all();

        if ($types === []) {
            return $query->whereRaw('1 = 0');
        }

        return $query->whereIn('policy_type', $types);
    }
}
