<?php

namespace App\Livewire;

use App\Jobs\AddPoliciesToBackupSetJob;
use App\Models\BackupSet;
use App\Models\BulkOperationRun;
use App\Models\Policy;
use App\Models\Tenant;
use App\Models\User;
use App\Services\BulkOperationService;
use App\Services\OperationRunService;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\OperationUxPresenter;
use App\Support\OpsUx\OpsUxBrowserEvents;
use App\Support\RunIdempotency;
use Filament\Actions\BulkAction;
use Filament\Notifications\Notification;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TernaryFilter;
use Filament\Tables\Table;
use Filament\Tables\TableComponent;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

class BackupSetPolicyPickerTable extends TableComponent
{
    public int $backupSetId;

    public bool $include_assignments = true;

    public bool $include_scope_tags = true;

    public bool $include_foundations = true;

    public function mount(int $backupSetId): void
    {
        $this->backupSetId = $backupSetId;
    }

    public static function externalIdShort(?string $externalId): string
    {
        $value = (string) ($externalId ?? '');

        $normalized = preg_replace('/[^A-Za-z0-9]/', '', $value) ?? '';

        if ($normalized === '') {
            return '—';
        }

        return substr($normalized, -8);
    }

    public function table(Table $table): Table
    {
        $backupSet = BackupSet::query()->find($this->backupSetId);
        $tenantId = $backupSet?->tenant_id ?? Tenant::current()->getKey();
        $existingPolicyIds = $backupSet
            ? $backupSet->items()->pluck('policy_id')->filter()->all()
            : [];

        return $table
            ->queryStringIdentifier('backupSetPolicyPicker'.Str::studly((string) $this->backupSetId))
            ->query(
                Policy::query()
                    ->where('tenant_id', $tenantId)
                    ->when($existingPolicyIds !== [], fn (Builder $query) => $query->whereNotIn('id', $existingPolicyIds))
            )
            ->deferLoading(! app()->runningUnitTests())
            ->paginated([25, 50, 100])
            ->defaultPaginationPageOption(25)
            ->searchable()
            ->striped()
            ->columns([
                TextColumn::make('display_name')
                    ->label('Name')
                    ->searchable()
                    ->sortable()
                    ->wrap(),
                TextColumn::make('policy_type')
                    ->label('Type')
                    ->badge()
                    ->formatStateUsing(fn (?string $state): string => (string) (static::typeMeta($state)['label'] ?? $state ?? '—')),
                TextColumn::make('platform')
                    ->label('Platform')
                    ->badge()
                    ->default('—')
                    ->sortable(),
                TextColumn::make('external_id')
                    ->label('External ID')
                    ->formatStateUsing(fn (?string $state): string => static::externalIdShort($state))
                    ->tooltip(fn (?string $state): ?string => filled($state) ? $state : null)
                    ->extraAttributes(['class' => 'font-mono text-xs'])
                    ->toggleable(),
                TextColumn::make('versions_count')
                    ->label('Versions')
                    ->state(fn (Policy $record): int => (int) ($record->versions_count ?? 0))
                    ->badge()
                    ->sortable(),
                TextColumn::make('last_synced_at')
                    ->label('Last synced')
                    ->dateTime()
                    ->since()
                    ->sortable()
                    ->toggleable(),
                TextColumn::make('ignored_at')
                    ->label('Ignored')
                    ->badge()
                    ->color(fn (?string $state): string => filled($state) ? 'warning' : 'gray')
                    ->formatStateUsing(fn (?string $state): string => filled($state) ? 'yes' : 'no')
                    ->toggleable(isToggledHiddenByDefault: true),
            ])
            ->modifyQueryUsing(fn (Builder $query) => $query->withCount('versions'))
            ->filters([
                SelectFilter::make('policy_type')
                    ->label('Policy type')
                    ->options(static::policyTypeOptions()),
                SelectFilter::make('platform')
                    ->label('Platform')
                    ->options(fn (): array => Policy::query()
                        ->where('tenant_id', $tenantId)
                        ->whereNotNull('platform')
                        ->distinct()
                        ->orderBy('platform')
                        ->pluck('platform', 'platform')
                        ->all()),
                SelectFilter::make('synced_within')
                    ->label('Last synced')
                    ->options([
                        '7' => 'Within 7 days',
                        '30' => 'Within 30 days',
                        '90' => 'Within 90 days',
                        'any' => 'Any time',
                    ])
                    ->default('7')
                    ->query(function (Builder $query, array $data): Builder {
                        $value = (string) ($data['value'] ?? '7');

                        if ($value === 'any') {
                            return $query;
                        }

                        $days = is_numeric($value) ? (int) $value : 7;

                        return $query->where('last_synced_at', '>', now()->subDays(max(1, $days)));
                    }),
                TernaryFilter::make('ignored')
                    ->label('Ignored')
                    ->nullable()
                    ->queries(
                        true: fn (Builder $query) => $query->whereNotNull('ignored_at'),
                        false: fn (Builder $query) => $query->whereNull('ignored_at'),
                    )
                    ->default(false),
                SelectFilter::make('has_versions')
                    ->label('Has versions')
                    ->options([
                        '1' => 'Has versions',
                        '0' => 'No versions',
                    ])
                    ->query(function (Builder $query, array $data): Builder {
                        $value = $data['value'] ?? null;

                        if ($value === null || $value === '') {
                            return $query;
                        }

                        return match ((string) $value) {
                            '1' => $query->whereHas('versions'),
                            '0' => $query->whereDoesntHave('versions'),
                            default => $query,
                        };
                    }),
            ])
            ->bulkActions([
                BulkAction::make('add_selected_to_backup_set')
                    ->label('Add selected')
                    ->icon('heroicon-m-plus')
                    ->authorize(function (): bool {
                        $this->dispatch(OpsUxBrowserEvents::RunEnqueued);
                        $user = auth()->user();

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

                        try {
                            $tenant = Tenant::current();
                        } catch (\RuntimeException) {
                            return false;
                        }

                        if (! $user->canSyncTenant($tenant)) {
                            return false;
                        }

                        return BackupSet::query()
                            ->whereKey($this->backupSetId)
                            ->where('tenant_id', $tenant->getKey())
                            ->exists();
                    })
                    ->action(function (Collection $records, BulkOperationService $bulkOperationService): void {
                        $backupSet = BackupSet::query()->findOrFail($this->backupSetId);
                        $tenant = null;

                        try {
                            $tenant = Tenant::current();
                        } catch (\RuntimeException) {
                            $tenant = $backupSet->tenant;
                        }
                        $user = auth()->user();

                        if (! $user instanceof User) {
                            Notification::make()
                                ->title('Not allowed')
                                ->danger()
                                ->send();

                            return;
                        }

                        if (! $tenant instanceof Tenant) {
                            Notification::make()
                                ->title('Not allowed')
                                ->danger()
                                ->send();

                            return;
                        }

                        if ((int) $tenant->getKey() !== (int) $backupSet->tenant_id) {
                            Notification::make()
                                ->title('Not allowed')
                                ->danger()
                                ->send();

                            return;
                        }

                        if (! $user->canSyncTenant($tenant)) {
                            Notification::make()
                                ->title('Not allowed')
                                ->danger()
                                ->send();

                            return;
                        }

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

                        if ($policyIds === []) {
                            Notification::make()
                                ->title('No policies selected')
                                ->warning()
                                ->send();

                            return;
                        }

                        sort($policyIds);

                        $idempotencyKey = RunIdempotency::buildKey(
                            tenantId: (int) $tenant->getKey(),
                            operationType: 'backup_set.add_policies',
                            targetId: (string) $backupSet->getKey(),
                            context: [
                                'policy_ids' => $policyIds,
                                'include_assignments' => (bool) $this->include_assignments,
                                'include_scope_tags' => (bool) $this->include_scope_tags,
                                'include_foundations' => (bool) $this->include_foundations,
                            ],
                        );

                        // --- Phase 3: Canonical Operation Run Start ---
                        /** @var OperationRunService $opService */
                        $opService = app(OperationRunService::class);
                        $opRun = $opService->ensureRun(
                            tenant: $tenant,
                            type: 'backup_set.add_policies',
                            inputs: [
                                'backup_set_id' => $backupSet->id,
                                'policy_ids' => $policyIds,
                                'options' => [
                                    'include_assignments' => (bool) $this->include_assignments,
                                    'include_scope_tags' => (bool) $this->include_scope_tags,
                                    'include_foundations' => (bool) $this->include_foundations,
                                ],
                            ],
                            initiator: $user
                        );

                        if (! $opRun->wasRecentlyCreated && in_array($opRun->status, ['queued', 'running'])) {
                            Notification::make()
                                ->title('Add policies already queued')
                                ->body('A matching run is already queued or running. Open the run to monitor progress.')
                                ->actions([
                                    \Filament\Actions\Action::make('view_run')
                                        ->label('View run')
                                        ->url(OperationRunLinks::view($opRun, $tenant)),
                                ])
                                ->info()
                                ->send();

                            return;
                        }
                        // ----------------------------------------------

                        $existingRun = RunIdempotency::findActiveBulkOperationRun(
                            tenantId: (int) $tenant->getKey(),
                            idempotencyKey: $idempotencyKey,
                        );

                        if ($existingRun instanceof BulkOperationRun) {
                            Notification::make()
                                ->title('Add policies already queued')
                                ->body('A matching run is already queued or running. Open the run to monitor progress.')
                                ->actions([
                                    \Filament\Actions\Action::make('view_run')
                                        ->label('View run')
                                        ->url(OperationRunLinks::view($opRun, $tenant)),
                                ])
                                ->info()
                                ->send();

                            return;
                        }

                        $selectionPayload = [
                            'backup_set_id' => (int) $backupSet->getKey(),
                            'policy_ids' => $policyIds,
                            'options' => [
                                'include_assignments' => (bool) $this->include_assignments,
                                'include_scope_tags' => (bool) $this->include_scope_tags,
                                'include_foundations' => (bool) $this->include_foundations,
                            ],
                        ];

                        try {
                            $run = $bulkOperationService->createRun(
                                tenant: $tenant,
                                user: $user,
                                resource: 'backup_set',
                                action: 'add_policies',
                                itemIds: $selectionPayload,
                                totalItems: count($policyIds),
                                idempotencyKey: $idempotencyKey,
                            );
                        } catch (QueryException $exception) {
                            if ((string) $exception->getCode() === '23505') {
                                $existingRun = RunIdempotency::findActiveBulkOperationRun(
                                    tenantId: (int) $tenant->getKey(),
                                    idempotencyKey: $idempotencyKey,
                                );

                                if ($existingRun instanceof BulkOperationRun) {
                                    Notification::make()
                                        ->title('Add policies already queued')
                                        ->body('A matching run is already queued or running. Open the run to monitor progress.')
                                        ->actions([
                                            \Filament\Actions\Action::make('view_run')
                                                ->label('View run')
                                                ->url(OperationRunLinks::view($opRun, $tenant)),
                                        ])
                                        ->info()
                                        ->send();

                                    return;
                                }
                            }

                            throw $exception;
                        }

                        /** @var OperationRunService $opService */
                        $opService = app(OperationRunService::class);
                        $opService->dispatchOrFail($opRun, function () use ($run, $backupSet, $opRun): void {
                            AddPoliciesToBackupSetJob::dispatch(
                                bulkRunId: (int) $run->getKey(),
                                backupSetId: (int) $backupSet->getKey(),
                                includeAssignments: (bool) $this->include_assignments,
                                includeScopeTags: (bool) $this->include_scope_tags,
                                includeFoundations: (bool) $this->include_foundations,
                                operationRun: $opRun
                            );
                        });

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

                        $this->resetTable();

                        $this->dispatch('backup-set-policy-picker:close')
                            ->to(\App\Filament\Resources\BackupSetResource\RelationManagers\BackupItemsRelationManager::class);
                    }),
            ]);
    }

    public function render(): View
    {
        return view('livewire.backup-set-policy-picker-table');
    }

    /**
     * @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 policyTypeOptions(): array
    {
        $types = array_merge(
            config('tenantpilot.supported_policy_types', []),
            config('tenantpilot.foundation_types', [])
        );

        return collect($types)
            ->mapWithKeys(function (array $meta): array {
                $type = (string) ($meta['type'] ?? '');

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

                $label = (string) ($meta['label'] ?? $type);

                return [$type => $label];
            })
            ->all();
    }
}
