<?php

namespace App\Filament\Resources;

use App\Filament\Resources\PolicyVersionResource\Pages;
use App\Jobs\BulkPolicyVersionForceDeleteJob;
use App\Jobs\BulkPolicyVersionPruneJob;
use App\Jobs\BulkPolicyVersionRestoreJob;
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\PolicyVersion;
use App\Models\Tenant;
use App\Services\BulkOperationService;
use App\Services\Intune\AuditLogger;
use App\Services\Intune\PolicyNormalizer;
use App\Services\Intune\VersionDiff;
use App\Support\OpsUx\OperationUxPresenter;
use BackedEnum;
use Carbon\CarbonImmutable;
use Filament\Actions;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
use Filament\Forms;
use Filament\Infolists;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
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\Support\Collection;
use UnitEnum;

class PolicyVersionResource extends Resource
{
    protected static ?string $model = PolicyVersion::class;

    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-list-bullet';

    protected static string|UnitEnum|null $navigationGroup = 'Inventory';

    public static function infolist(Schema $schema): Schema
    {
        return $schema
            ->schema([
                Infolists\Components\TextEntry::make('policy.display_name')->label('Policy'),
                Infolists\Components\TextEntry::make('version_number')->label('Version'),
                Infolists\Components\TextEntry::make('policy_type'),
                Infolists\Components\TextEntry::make('platform'),
                Infolists\Components\TextEntry::make('created_by')->label('Actor'),
                Infolists\Components\TextEntry::make('captured_at')->dateTime(),
                Tabs::make()
                    ->activeTab(1)
                    ->persistTabInQueryString('tab')
                    ->columnSpanFull()
                    ->tabs([
                        Tab::make('Normalized settings')
                            ->id('normalized-settings')
                            ->schema([
                                Infolists\Components\ViewEntry::make('normalized_settings_catalog')
                                    ->view('filament.infolists.entries.normalized-settings')
                                    ->state(function (PolicyVersion $record) {
                                        $normalized = app(PolicyNormalizer::class)->normalize(
                                            is_array($record->snapshot) ? $record->snapshot : [],
                                            $record->policy_type ?? '',
                                            $record->platform
                                        );

                                        $normalized['context'] = 'version';
                                        $normalized['record_id'] = (string) $record->getKey();

                                        return $normalized;
                                    })
                                    ->visible(fn (PolicyVersion $record) => in_array($record->policy_type, ['settingsCatalogPolicy', 'endpointSecurityPolicy', 'securityBaselinePolicy'], true)),

                                Infolists\Components\ViewEntry::make('normalized_settings_standard')
                                    ->view('filament.infolists.entries.policy-settings-standard')
                                    ->state(function (PolicyVersion $record) {
                                        $normalized = app(PolicyNormalizer::class)->normalize(
                                            is_array($record->snapshot) ? $record->snapshot : [],
                                            $record->policy_type ?? '',
                                            $record->platform
                                        );

                                        $normalized['context'] = 'version';
                                        $normalized['record_id'] = (string) $record->getKey();
                                        $normalized['policy_type'] = $record->policy_type;

                                        return $normalized;
                                    })
                                    ->visible(fn (PolicyVersion $record) => ! in_array($record->policy_type, ['settingsCatalogPolicy', 'endpointSecurityPolicy', 'securityBaselinePolicy'], true)),
                            ]),
                        Tab::make('Raw JSON')
                            ->id('raw-json')
                            ->schema([
                                Infolists\Components\ViewEntry::make('snapshot_pretty')
                                    ->view('filament.infolists.entries.snapshot-json')
                                    ->state(fn (PolicyVersion $record) => $record->snapshot ?? []),
                            ]),
                        Tab::make('Diff')
                            ->id('diff')
                            ->schema([
                                Infolists\Components\ViewEntry::make('normalized_diff')
                                    ->view('filament.infolists.entries.normalized-diff')
                                    ->state(function (PolicyVersion $record) {
                                        $normalizer = app(PolicyNormalizer::class);
                                        $diff = app(VersionDiff::class);

                                        $previous = $record->previous();
                                        $from = $previous
                                            ? $normalizer->flattenForDiff($previous->snapshot ?? [], $previous->policy_type ?? '', $previous->platform)
                                            : [];
                                        $to = $normalizer->flattenForDiff($record->snapshot ?? [], $record->policy_type ?? '', $record->platform);

                                        $result = $diff->compare($from, $to);
                                        $result['policy_type'] = $record->policy_type;

                                        return $result;
                                    }),
                                Infolists\Components\ViewEntry::make('diff_json')
                                    ->label('Raw diff (advanced)')
                                    ->view('filament.infolists.entries.snapshot-json')
                                    ->state(function (PolicyVersion $record) {
                                        $previous = $record->previous();

                                        if (! $previous) {
                                            return ['summary' => 'No previous version'];
                                        }

                                        $diff = app(VersionDiff::class)->compare(
                                            $previous->snapshot ?? [],
                                            $record->snapshot ?? []
                                        );

                                        $filter = static fn (array $items): array => array_filter(
                                            $items,
                                            static fn (mixed $value, string $key): bool => ! str_contains($key, '@odata.context'),
                                            ARRAY_FILTER_USE_BOTH
                                        );

                                        $added = $filter($diff['added'] ?? []);
                                        $removed = $filter($diff['removed'] ?? []);
                                        $changed = $filter($diff['changed'] ?? []);

                                        return [
                                            'summary' => [
                                                'added' => count($added),
                                                'removed' => count($removed),
                                                'changed' => count($changed),
                                                'message' => sprintf(
                                                    '%d added, %d removed, %d changed',
                                                    count($added),
                                                    count($removed),
                                                    count($changed)
                                                ),
                                            ],
                                            'added' => $added,
                                            'removed' => $removed,
                                            'changed' => $changed,
                                        ];
                                    }),
                            ]),
                    ]),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('policy.display_name')->label('Policy')->sortable()->searchable(),
                Tables\Columns\TextColumn::make('version_number')->sortable(),
                Tables\Columns\TextColumn::make('policy_type')->badge(),
                Tables\Columns\TextColumn::make('platform')->badge(),
                Tables\Columns\TextColumn::make('created_by')->label('Actor'),
                Tables\Columns\TextColumn::make('captured_at')->dateTime()->sortable(),
            ])
            ->filters([
                TrashedFilter::make()
                    ->label('Archived')
                    ->placeholder('Active')
                    ->trueLabel('All')
                    ->falseLabel('Archived'),
            ])
            ->actions([
                Actions\ViewAction::make(),
                Actions\ActionGroup::make([
                    Actions\Action::make('restore_via_wizard')
                        ->label('Restore via Wizard')
                        ->icon('heroicon-o-arrow-path-rounded-square')
                        ->color('primary')
                        ->disabled(fn (PolicyVersion $record): bool => ($record->metadata['source'] ?? null) === 'metadata_only')
                        ->tooltip('Disabled for metadata-only snapshots (Graph did not provide policy settings).')
                        ->requiresConfirmation()
                        ->modalHeading(fn (PolicyVersion $record): string => "Restore version {$record->version_number} via wizard?")
                        ->modalSubheading('Creates a 1-item backup set from this snapshot and opens the restore run wizard prefilled.')
                        ->action(function (PolicyVersion $record) {
                            $tenant = Tenant::current();
                            $user = auth()->user();

                            if (! $tenant || $record->tenant_id !== $tenant->id) {
                                Notification::make()
                                    ->title('Policy version belongs to a different tenant')
                                    ->danger()
                                    ->send();

                                return;
                            }

                            $policy = $record->policy;

                            if (! $policy) {
                                Notification::make()
                                    ->title('Policy could not be found for this version')
                                    ->danger()
                                    ->send();

                                return;
                            }

                            $backupSet = BackupSet::create([
                                'tenant_id' => $tenant->id,
                                'name' => sprintf(
                                    'Policy Version Restore • %s • v%d',
                                    $policy->display_name,
                                    $record->version_number
                                ),
                                'created_by' => $user?->email,
                                'status' => 'completed',
                                'item_count' => 1,
                                'completed_at' => CarbonImmutable::now(),
                                'metadata' => [
                                    'source' => 'policy_version',
                                    'policy_version_id' => $record->id,
                                    'policy_version_number' => $record->version_number,
                                    'policy_id' => $policy->id,
                                ],
                            ]);

                            $scopeTags = is_array($record->scope_tags) ? $record->scope_tags : [];
                            $scopeTagIds = $scopeTags['ids'] ?? null;
                            $scopeTagNames = $scopeTags['names'] ?? null;

                            $backupItemMetadata = [
                                'source' => 'policy_version',
                                'display_name' => $policy->display_name,
                                'policy_version_id' => $record->id,
                                'policy_version_number' => $record->version_number,
                                'version_captured_at' => $record->captured_at?->toIso8601String(),
                            ];

                            if (is_array($scopeTagIds) && $scopeTagIds !== []) {
                                $backupItemMetadata['scope_tag_ids'] = $scopeTagIds;
                            }

                            if (is_array($scopeTagNames) && $scopeTagNames !== []) {
                                $backupItemMetadata['scope_tag_names'] = $scopeTagNames;
                            }

                            $backupItem = BackupItem::create([
                                'tenant_id' => $tenant->id,
                                'backup_set_id' => $backupSet->id,
                                'policy_id' => $policy->id,
                                'policy_version_id' => $record->id,
                                'policy_identifier' => $policy->external_id,
                                'policy_type' => $policy->policy_type,
                                'platform' => $policy->platform,
                                'captured_at' => $record->captured_at ?? CarbonImmutable::now(),
                                'payload' => $record->snapshot ?? [],
                                'metadata' => $backupItemMetadata,
                                'assignments' => $record->assignments,
                            ]);

                            return redirect()->to(RestoreRunResource::getUrl('create', [
                                'backup_set_id' => $backupSet->id,
                                'scope_mode' => 'selected',
                                'backup_item_ids' => [$backupItem->id],
                            ]));
                        }),
                    Actions\Action::make('archive')
                        ->label('Archive')
                        ->color('danger')
                        ->icon('heroicon-o-archive-box-x-mark')
                        ->requiresConfirmation()
                        ->visible(fn (PolicyVersion $record) => ! $record->trashed())
                        ->action(function (PolicyVersion $record, AuditLogger $auditLogger) {
                            $record->delete();

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

                            Notification::make()
                                ->title('Policy version archived')
                                ->success()
                                ->send();
                        }),
                    Actions\Action::make('forceDelete')
                        ->label('Force delete')
                        ->color('danger')
                        ->icon('heroicon-o-trash')
                        ->requiresConfirmation()
                        ->visible(fn (PolicyVersion $record) => $record->trashed())
                        ->action(function (PolicyVersion $record, AuditLogger $auditLogger) {
                            if ($record->tenant) {
                                $auditLogger->log(
                                    tenant: $record->tenant,
                                    action: 'policy_version.force_deleted',
                                    resourceType: 'policy_version',
                                    resourceId: (string) $record->id,
                                    status: 'success',
                                    context: ['metadata' => ['policy_id' => $record->policy_id, 'version' => $record->version_number]]
                                );
                            }

                            $record->forceDelete();

                            Notification::make()
                                ->title('Policy version permanently deleted')
                                ->success()
                                ->send();
                        }),

                    Actions\Action::make('restore')
                        ->label('Restore')
                        ->color('success')
                        ->icon('heroicon-o-arrow-uturn-left')
                        ->requiresConfirmation()
                        ->visible(fn (PolicyVersion $record) => $record->trashed())
                        ->action(function (PolicyVersion $record, AuditLogger $auditLogger) {
                            $record->restore();

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

                            Notification::make()
                                ->title('Policy version restored')
                                ->success()
                                ->send();
                        }),
                ])->icon('heroicon-o-ellipsis-vertical'),
            ])
            ->bulkActions([
                BulkActionGroup::make([
                    BulkAction::make('bulk_prune_versions')
                        ->label('Prune Versions')
                        ->icon('heroicon-o-trash')
                        ->color('danger')
                        ->requiresConfirmation()
                        ->modalDescription('Only versions captured more than the specified retention window (in days) are eligible. Newer versions will be skipped.')
                        ->hidden(function (HasTable $livewire): bool {
                            $trashedFilterState = $livewire->getTableFilterState(TrashedFilter::class) ?? [];
                            $value = $trashedFilterState['value'] ?? null;

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

                            return $isOnlyTrashed;
                        })
                        ->form(function (Collection $records) {
                            $fields = [
                                Forms\Components\TextInput::make('retention_days')
                                    ->label('Retention Days')
                                    ->helperText('Versions captured within the last N days will be skipped.')
                                    ->numeric()
                                    ->required()
                                    ->default(90)
                                    ->minValue(1),
                            ];

                            if ($records->count() >= 20) {
                                $fields[] = Forms\Components\TextInput::make('confirmation')
                                    ->label('Type DELETE to confirm')
                                    ->required()
                                    ->in(['DELETE'])
                                    ->validationMessages([
                                        'in' => 'Please type DELETE to confirm.',
                                    ]);
                            }

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

                            $retentionDays = (int) ($data['retention_days'] ?? 90);

                            $service = app(BulkOperationService::class);
                            $run = $service->createRun($tenant, $user, 'policy_version', 'prune', $ids, $count);

                            if ($count >= 20) {
                                OperationUxPresenter::queuedToast('policy_version.prune')
                                    ->actions([
                                        Actions\Action::make('view_run')
                                            ->label('View run')
                                            ->url(BulkOperationRunResource::getUrl('view', ['record' => $run], tenant: $tenant)),
                                    ])
                                    ->send();

                                BulkPolicyVersionPruneJob::dispatch($run->id, $retentionDays);
                            } else {
                                BulkPolicyVersionPruneJob::dispatchSync($run->id, $retentionDays);
                            }
                        })
                        ->deselectRecordsAfterCompletion(),

                    BulkAction::make('bulk_restore_versions')
                        ->label('Restore Versions')
                        ->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()} policy versions?")
                        ->modalDescription('Archived versions will be restored back to the active list. Active versions will be skipped.')
                        ->action(function (Collection $records) {
                            $tenant = Tenant::current();
                            $user = auth()->user();
                            $count = $records->count();
                            $ids = $records->pluck('id')->toArray();

                            $service = app(BulkOperationService::class);
                            $run = $service->createRun($tenant, $user, 'policy_version', 'restore', $ids, $count);

                            if ($count >= 20) {
                                OperationUxPresenter::queuedToast('policy_version.restore')
                                    ->actions([
                                        Actions\Action::make('view_run')
                                            ->label('View run')
                                            ->url(BulkOperationRunResource::getUrl('view', ['record' => $run], tenant: $tenant)),
                                    ])
                                    ->send();

                                BulkPolicyVersionRestoreJob::dispatch($run->id);
                            } else {
                                BulkPolicyVersionRestoreJob::dispatchSync($run->id);
                            }
                        })
                        ->deselectRecordsAfterCompletion(),

                    BulkAction::make('bulk_force_delete_versions')
                        ->label('Force Delete Versions')
                        ->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()} policy versions?")
                        ->modalDescription('This is permanent. Only archived versions will be permanently deleted; active versions will be skipped.')
                        ->form([
                            Forms\Components\TextInput::make('confirmation')
                                ->label('Type DELETE to confirm')
                                ->required()
                                ->in(['DELETE'])
                                ->validationMessages([
                                    'in' => 'Please type DELETE to confirm.',
                                ]),
                        ])
                        ->action(function (Collection $records, array $data) {
                            $tenant = Tenant::current();
                            $user = auth()->user();
                            $count = $records->count();
                            $ids = $records->pluck('id')->toArray();

                            $service = app(BulkOperationService::class);
                            $run = $service->createRun($tenant, $user, 'policy_version', 'force_delete', $ids, $count);

                            if ($count >= 20) {
                                OperationUxPresenter::queuedToast('policy_version.force_delete')
                                    ->actions([
                                        Actions\Action::make('view_run')
                                            ->label('View run')
                                            ->url(BulkOperationRunResource::getUrl('view', ['record' => $run], tenant: $tenant)),
                                    ])
                                    ->send();

                                BulkPolicyVersionForceDeleteJob::dispatch($run->id);
                            } else {
                                BulkPolicyVersionForceDeleteJob::dispatchSync($run->id);
                            }
                        })
                        ->deselectRecordsAfterCompletion(),
                ]),
            ]);
    }

    public static function getEloquentQuery(): Builder
    {
        $tenantId = Tenant::current()->getKey();

        return parent::getEloquentQuery()
            ->when($tenantId, fn (Builder $query) => $query->where('tenant_id', $tenantId))
            ->with('policy');
    }

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