<?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\BackupSet;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\OperationRunService;
use App\Support\Auth\Capabilities;
use App\Support\BackupQuality\BackupQualityResolver;
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 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 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();
    }

    /**
     * @param  array<string, mixed>  $arguments
     * @param  array<string, mixed>  $context
     */
    public function mountAction(string $name, array $arguments = [], array $context = []): mixed
    {
        if (($context['table'] ?? false) === true) {
            $backupSet = $this->getOwnerRecord();

            if ($name === 'remove' && filled($context['recordKey'] ?? null)) {
                $this->resolveOwnerScopedBackupItemId($backupSet, $context['recordKey']);
            }

            if ($name === 'bulk_remove' && ($context['bulk'] ?? false) === true) {
                $this->resolveOwnerScopedBackupItemIdsFromKeys($backupSet, $this->selectedTableRecords);
            }
        }

        return parent::mountAction($name, $arguments, $context);
    }

    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forRelationManager(ActionSurfaceProfile::RelationManager)
            ->satisfy(ActionSurfaceSlot::ListHeader, 'Refresh and Add Policies actions are available in the relation header.')
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
            ->satisfy(ActionSurfaceSlot::ListRowMoreMenu, 'Clickable-row inspection stays primary while Remove remains grouped under More.')
            ->satisfy(ActionSurfaceSlot::ListBulkMoreGroup, 'Bulk remove remains grouped under More.')
            ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty state exposes the Add Policies CTA.');
    }

    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 (mixed $record): void {
                $backupSet = $this->getOwnerRecord();

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

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

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

                $backupItemIds = [$this->resolveOwnerScopedBackupItemId($backupSet, $record)];

                /** @var OperationRunService $opService */
                $opService = app(OperationRunService::class);
                $opRun = $opService->ensureRun(
                    tenant: $tenant,
                    type: 'backup_set.update',
                    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('Open operation')
                                ->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('Open operation')
                            ->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 ?? ManagedEnvironment::current();
                if (! $tenant instanceof ManagedEnvironment) {
                    abort(404);
                }

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

                $backupItemIds = $this->resolveOwnerScopedBackupItemIdsFromKeys($backupSet, $this->selectedTableRecords);

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

                /** @var OperationRunService $opService */
                $opService = app(OperationRunService::class);
                $opRun = $opService->ensureRun(
                    tenant: $tenant,
                    type: 'backup_set.update',
                    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('Open operation')
                                ->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('Open operation')
                            ->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()
            ->recordUrl(fn (BackupItem $record): ?string => $this->backupItemInspectUrl($record))
            ->columns([
                Tables\Columns\TextColumn::make('policy.display_name')
                    ->label('Item')
                    ->sortable()
                    ->searchable()
                    ->getStateUsing(fn (BackupItem $record) => $record->resolvedDisplayName()),
                Tables\Columns\TextColumn::make('snapshot_mode')
                    ->label('Snapshot')
                    ->badge()
                    ->state(fn (BackupItem $record): string => $this->backupItemQualitySummary($record)->snapshotMode)
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::PolicySnapshotMode))
                    ->color(BadgeRenderer::color(BadgeDomain::PolicySnapshotMode))
                    ->icon(BadgeRenderer::icon(BadgeDomain::PolicySnapshotMode))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::PolicySnapshotMode)),
                Tables\Columns\TextColumn::make('policyVersion.version_number')
                    ->label('Version')
                    ->badge()
                    ->default('—')
                    ->getStateUsing(fn (BackupItem $record): ?int => $record->policyVersion?->version_number),
                Tables\Columns\TextColumn::make('backup_quality')
                    ->label('Backup quality')
                    ->state(fn (BackupItem $record): string => $this->backupItemQualitySummary($record)->compactSummary)
                    ->description(function (BackupItem $record): string {
                        $summary = $this->backupItemQualitySummary($record);

                        if ($summary->assignmentCaptureReason === 'separate_role_assignments') {
                            return 'Assignments are captured separately for this item type.';
                        }

                        return $summary->nextAction;
                    })
                    ->wrap(),
                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([
                    $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);
    }

    private function backupItemInspectUrl(BackupItem $record): ?string
    {
        $backupSet = $this->getOwnerRecord();
        $resolvedId = $this->resolveOwnerScopedBackupItemId($backupSet, $record);

        $resolvedRecord = $backupSet->items()
            ->with(['policy', 'policyVersion', 'policyVersion.policy'])
            ->where('managed_environment_id', (int) $backupSet->managed_environment_id)
            ->whereKey($resolvedId)
            ->first();

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

        $tenant = $backupSet->tenant ?? ManagedEnvironment::current();

        if (! $tenant instanceof ManagedEnvironment) {
            return null;
        }

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

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

        return PolicyResource::getUrl('view', ['record' => $resolvedRecord->policy_id], tenant: $tenant);
    }

    private function backupItemQualitySummary(BackupItem $record): \App\Support\BackupQuality\BackupQualitySummary
    {
        return app(BackupQualityResolver::class)->forBackupItem($record);
    }

    private function resolveOwnerScopedBackupItemId(BackupSet $backupSet, mixed $record): int
    {
        $recordId = $this->normalizeBackupItemKey($record);

        if ($recordId <= 0) {
            abort(404);
        }

        $resolvedId = $backupSet->items()
            ->where('managed_environment_id', (int) $backupSet->managed_environment_id)
            ->whereKey($recordId)
            ->value('id');

        if (! is_numeric($resolvedId) || (int) $resolvedId <= 0) {
            abort(404);
        }

        return (int) $resolvedId;
    }

    /**
     * @return array<int, int>
     */
    private function resolveOwnerScopedBackupItemIdsFromKeys(BackupSet $backupSet, array $recordKeys): array
    {
        $requestedIds = collect($recordKeys)
            ->map(fn (mixed $record): int => $this->normalizeBackupItemKey($record))
            ->filter(fn (int $value): bool => $value > 0)
            ->unique()
            ->sort()
            ->values()
            ->all();

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

        $resolvedIds = $backupSet->items()
            ->where('managed_environment_id', (int) $backupSet->managed_environment_id)
            ->whereIn('id', $requestedIds)
            ->pluck('id')
            ->map(fn (mixed $value): int => (int) $value)
            ->filter(fn (int $value): bool => $value > 0)
            ->unique()
            ->sort()
            ->values()
            ->all();

        if (count($resolvedIds) !== count($requestedIds)) {
            abort(404);
        }

        return $resolvedIds;
    }

    private function normalizeBackupItemKey(mixed $record): int
    {
        if ($record instanceof BackupItem) {
            return (int) $record->getKey();
        }

        return is_numeric($record) ? (int) $record : 0;
    }
}
