<?php

namespace App\Livewire;

use App\Jobs\AddPoliciesToBackupSetJob;
use App\Models\BackupSet;
use App\Models\Policy;
use App\Models\ManagedEnvironment;
use App\Models\User;
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\Badges\TagBadgeDomain;
use App\Support\Badges\TagBadgeRenderer;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\OperationUxPresenter;
use App\Support\OpsUx\OpsUxBrowserEvents;
use Filament\Actions\BulkAction;
use Filament\Notifications\Notification;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Filament\Tables\TableComponent;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
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?->managed_environment_id ?? ManagedEnvironment::currentOrFail()->getKey();
        $existingPolicyIds = $backupSet
            ? $backupSet->items()->pluck('policy_id')->filter()->all()
            : [];

        return $table
            ->queryStringIdentifier('backupSetPolicyPicker'.Str::studly((string) $this->backupSetId))
            ->query(
                Policy::query()
                    ->where('managed_environment_id', $tenantId)
                    ->when($existingPolicyIds !== [], fn (Builder $query) => $query->whereNotIn('id', $existingPolicyIds))
            )
            ->deferLoading(! app()->runningUnitTests())
            ->defaultSort('display_name')
            ->paginated(\App\Support\Filament\TablePaginationProfiles::picker())
            ->defaultPaginationPageOption(25)
            ->searchable()
            ->striped()
            ->columns([
                TextColumn::make('display_name')
                    ->label('Name')
                    ->searchable()
                    ->sortable()
                    ->wrap(),
                TextColumn::make('policy_type')
                    ->label('Type')
                    ->badge()
                    ->placeholder('—')
                    ->formatStateUsing(TagBadgeRenderer::label(TagBadgeDomain::PolicyType))
                    ->color(TagBadgeRenderer::color(TagBadgeDomain::PolicyType)),
                TextColumn::make('platform')
                    ->label('Platform')
                    ->badge()
                    ->placeholder('—')
                    ->formatStateUsing(TagBadgeRenderer::label(TagBadgeDomain::Platform))
                    ->color(TagBadgeRenderer::color(TagBadgeDomain::Platform))
                    ->sortable(),
                TextColumn::make('visibility_state')
                    ->label('Visibility')
                    ->badge()
                    ->state(fn (Policy $record): string => $record->visibilityState())
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::PolicyProviderPresence))
                    ->color(BadgeRenderer::color(BadgeDomain::PolicyProviderPresence))
                    ->icon(BadgeRenderer::icon(BadgeDomain::PolicyProviderPresence))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::PolicyProviderPresence))
                    ->description(fn (Policy $record): ?string => $record->isCurrentBackupEligible()
                        ? null
                        : $record->currentBackupBlockedReasonLabel()),
                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()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::IgnoredAt))
                    ->color(BadgeRenderer::color(BadgeDomain::IgnoredAt))
                    ->icon(BadgeRenderer::icon(BadgeDomain::IgnoredAt))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::IgnoredAt))
                    ->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('managed_environment_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('any')
                    ->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)));
                    }),
                SelectFilter::make('visibility')
                    ->label('Visibility')
                    ->options([
                        'active' => 'Active',
                        'ignored' => 'Ignored locally',
                        'provider_missing' => 'Provider missing',
                        'all' => 'All',
                    ])
                    ->query(function (Builder $query, array $data): Builder {
                        $value = $data['value'] ?? null;

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

                        return match ($value) {
                            'active' => $query->active(),
                            'ignored' => $query->whereNotNull('ignored_at'),
                            'provider_missing' => $query->whereNotNull('missing_from_provider_at'),
                            default => $query,
                        };
                    }),
                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,
                        };
                    }),
            ])
            ->emptyStateHeading('No matching policies available')
            ->emptyStateDescription('Adjust the current filters or sync additional policies before adding them to this backup set.')
            ->checkIfRecordIsSelectableUsing(fn (Policy $record): bool => $record->isCurrentBackupEligible())
            ->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 = ManagedEnvironment::current();
                        } catch (\RuntimeException) {
                            return false;
                        }

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

                        if (! Gate::forUser($user)->allows(Capabilities::TENANT_SYNC, $tenant)) {
                            return false;
                        }

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

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

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

                            return;
                        }

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

                            return;
                        }

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

                            return;
                        }

                        if (! Gate::forUser($user)->allows(Capabilities::TENANT_SYNC, $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);

                        $blocked = $records->first(
                            fn ($record): bool => $record instanceof Policy && ! $record->isCurrentBackupEligible()
                        );

                        if ($blocked instanceof Policy) {
                            Notification::make()
                                ->title('Current backup unavailable')
                                ->body($blocked->currentBackupBlockedReasonLabel())
                                ->warning()
                                ->send();

                            return;
                        }

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

                        /** @var OperationRunService $opService */
                        $opService = app(OperationRunService::class);
                        $opRun = $opService->enqueueBulkOperation(
                            tenant: $tenant,
                            type: 'backup_set.update',
                            targetScope: [
                                'entra_tenant_id' => (string) ($tenant->managed_environment_id ?? $tenant->external_id),
                            ],
                            selectionIdentity: $selectionIdentity,
                            dispatcher: function ($operationRun) use ($tenant, $user, $backupSet, $policyIds): void {
                                $fingerprint = (string) data_get($operationRun?->context ?? [], 'idempotency.fingerprint', '');

                                AddPoliciesToBackupSetJob::dispatch(
                                    tenantId: (int) $tenant->getKey(),
                                    userId: (int) $user->getKey(),
                                    backupSetId: (int) $backupSet->getKey(),
                                    policyIds: $policyIds,
                                    options: [
                                        'include_assignments' => (bool) $this->include_assignments,
                                        'include_scope_tags' => (bool) $this->include_scope_tags,
                                        'include_foundations' => (bool) $this->include_foundations,
                                    ],
                                    idempotencyKey: $fingerprint,
                                    operationRun: $operationRun,
                                );
                            },
                            initiator: $user,
                            extraContext: [
                                'backup_set_id' => (int) $backupSet->getKey(),
                                'policy_count' => count($policyIds),
                            ],
                        );

                        if (! $opRun->wasRecentlyCreated && in_array($opRun->status, ['queued', 'running'], true)) {
                            OperationUxPresenter::alreadyQueuedToast((string) $opRun->type)
                                ->actions([
                                    \Filament\Actions\Action::make('view_run')
                                        ->label('Open operation')
                                        ->url(OperationRunLinks::view($opRun, $tenant)),
                                ])
                                ->send();

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

                        $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();
    }
}
