<?php

namespace App\Filament\Resources;

use App\Filament\Resources\FindingResource\Pages;
use App\Models\Finding;
use App\Models\InventoryItem;
use App\Models\PolicyVersion;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Drift\DriftFindingDiffBuilder;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use BackedEnum;
use Filament\Actions;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Components\ViewEntry;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
use UnitEnum;

class FindingResource extends Resource
{
    protected static ?string $model = Finding::class;

    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-exclamation-triangle';

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

    protected static ?string $navigationLabel = 'Findings';

    public static function form(Schema $schema): Schema
    {
        return $schema;
    }

    public static function infolist(Schema $schema): Schema
    {
        return $schema
            ->schema([
                Section::make('Finding')
                    ->schema([
                        TextEntry::make('finding_type')->badge()->label('Type'),
                        TextEntry::make('status')
                            ->badge()
                            ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingStatus))
                            ->color(BadgeRenderer::color(BadgeDomain::FindingStatus))
                            ->icon(BadgeRenderer::icon(BadgeDomain::FindingStatus))
                            ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingStatus)),
                        TextEntry::make('severity')
                            ->badge()
                            ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingSeverity))
                            ->color(BadgeRenderer::color(BadgeDomain::FindingSeverity))
                            ->icon(BadgeRenderer::icon(BadgeDomain::FindingSeverity))
                            ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingSeverity)),
                        TextEntry::make('fingerprint')->label('Fingerprint')->copyable(),
                        TextEntry::make('scope_key')->label('Scope')->copyable(),
                        TextEntry::make('subject_display_name')->label('Subject')->placeholder('—'),
                        TextEntry::make('subject_type')->label('Subject type'),
                        TextEntry::make('subject_external_id')->label('External ID')->copyable(),
                        TextEntry::make('baseline_run_id')
                            ->label('Baseline run')
                            ->url(fn (Finding $record): ?string => $record->baseline_run_id
                                ? InventorySyncRunResource::getUrl('view', ['record' => $record->baseline_run_id], tenant: Tenant::current())
                                : null)
                            ->openUrlInNewTab(),
                        TextEntry::make('current_run_id')
                            ->label('Current run')
                            ->url(fn (Finding $record): ?string => $record->current_run_id
                                ? InventorySyncRunResource::getUrl('view', ['record' => $record->current_run_id], tenant: Tenant::current())
                                : null)
                            ->openUrlInNewTab(),
                        TextEntry::make('acknowledged_at')->dateTime()->placeholder('—'),
                        TextEntry::make('acknowledged_by_user_id')->label('Acknowledged by')->placeholder('—'),
                        TextEntry::make('created_at')->label('Created')->dateTime(),
                    ])
                    ->columns(2)
                    ->columnSpanFull(),

                Section::make('Diff')
                    ->schema([
                        ViewEntry::make('settings_diff')
                            ->label('')
                            ->view('filament.infolists.entries.normalized-diff')
                            ->state(function (Finding $record): array {
                                $tenant = Tenant::current();
                                if (! $tenant) {
                                    return ['summary' => ['message' => 'No tenant context'], 'added' => [], 'removed' => [], 'changed' => []];
                                }

                                $baselineId = Arr::get($record->evidence_jsonb ?? [], 'baseline.policy_version_id');
                                $currentId = Arr::get($record->evidence_jsonb ?? [], 'current.policy_version_id');

                                $baselineVersion = is_numeric($baselineId)
                                    ? PolicyVersion::query()->where('tenant_id', $tenant->getKey())->find((int) $baselineId)
                                    : null;

                                $currentVersion = is_numeric($currentId)
                                    ? PolicyVersion::query()->where('tenant_id', $tenant->getKey())->find((int) $currentId)
                                    : null;

                                $diff = app(DriftFindingDiffBuilder::class)->buildSettingsDiff($baselineVersion, $currentVersion);

                                $addedCount = (int) Arr::get($diff, 'summary.added', 0);
                                $removedCount = (int) Arr::get($diff, 'summary.removed', 0);
                                $changedCount = (int) Arr::get($diff, 'summary.changed', 0);

                                if (($addedCount + $removedCount + $changedCount) === 0) {
                                    Arr::set(
                                        $diff,
                                        'summary.message',
                                        'No normalized changes were found. This drift finding may be based on fields that are intentionally excluded from normalization.'
                                    );
                                }

                                return $diff;
                            })
                            ->visible(fn (Finding $record): bool => Arr::get($record->evidence_jsonb ?? [], 'summary.kind') === 'policy_snapshot')
                            ->columnSpanFull(),

                        ViewEntry::make('scope_tags_diff')
                            ->label('')
                            ->view('filament.infolists.entries.scope-tags-diff')
                            ->state(function (Finding $record): array {
                                $tenant = Tenant::current();
                                if (! $tenant) {
                                    return ['summary' => ['message' => 'No tenant context'], 'added' => [], 'removed' => [], 'changed' => []];
                                }

                                $baselineId = Arr::get($record->evidence_jsonb ?? [], 'baseline.policy_version_id');
                                $currentId = Arr::get($record->evidence_jsonb ?? [], 'current.policy_version_id');

                                $baselineVersion = is_numeric($baselineId)
                                    ? PolicyVersion::query()->where('tenant_id', $tenant->getKey())->find((int) $baselineId)
                                    : null;

                                $currentVersion = is_numeric($currentId)
                                    ? PolicyVersion::query()->where('tenant_id', $tenant->getKey())->find((int) $currentId)
                                    : null;

                                return app(DriftFindingDiffBuilder::class)->buildScopeTagsDiff($baselineVersion, $currentVersion);
                            })
                            ->visible(fn (Finding $record): bool => Arr::get($record->evidence_jsonb ?? [], 'summary.kind') === 'policy_scope_tags')
                            ->columnSpanFull(),

                        ViewEntry::make('assignments_diff')
                            ->label('')
                            ->view('filament.infolists.entries.assignments-diff')
                            ->state(function (Finding $record): array {
                                $tenant = Tenant::current();
                                if (! $tenant) {
                                    return ['summary' => ['message' => 'No tenant context'], 'added' => [], 'removed' => [], 'changed' => []];
                                }

                                $baselineId = Arr::get($record->evidence_jsonb ?? [], 'baseline.policy_version_id');
                                $currentId = Arr::get($record->evidence_jsonb ?? [], 'current.policy_version_id');

                                $baselineVersion = is_numeric($baselineId)
                                    ? PolicyVersion::query()->where('tenant_id', $tenant->getKey())->find((int) $baselineId)
                                    : null;

                                $currentVersion = is_numeric($currentId)
                                    ? PolicyVersion::query()->where('tenant_id', $tenant->getKey())->find((int) $currentId)
                                    : null;

                                return app(DriftFindingDiffBuilder::class)->buildAssignmentsDiff($tenant, $baselineVersion, $currentVersion);
                            })
                            ->visible(fn (Finding $record): bool => Arr::get($record->evidence_jsonb ?? [], 'summary.kind') === 'policy_assignments')
                            ->columnSpanFull(),
                    ])
                    ->collapsed()
                    ->columnSpanFull(),

                Section::make('Evidence (Sanitized)')
                    ->schema([
                        ViewEntry::make('evidence_jsonb')
                            ->label('')
                            ->view('filament.infolists.entries.snapshot-json')
                            ->state(fn (Finding $record) => $record->evidence_jsonb ?? [])
                            ->columnSpanFull(),
                    ])
                    ->columnSpanFull(),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->defaultSort('created_at', 'desc')
            ->columns([
                Tables\Columns\TextColumn::make('finding_type')->badge()->label('Type'),
                Tables\Columns\TextColumn::make('status')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingStatus))
                    ->color(BadgeRenderer::color(BadgeDomain::FindingStatus))
                    ->icon(BadgeRenderer::icon(BadgeDomain::FindingStatus))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingStatus)),
                Tables\Columns\TextColumn::make('severity')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingSeverity))
                    ->color(BadgeRenderer::color(BadgeDomain::FindingSeverity))
                    ->icon(BadgeRenderer::icon(BadgeDomain::FindingSeverity))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingSeverity)),
                Tables\Columns\TextColumn::make('subject_display_name')->label('Subject')->placeholder('—'),
                Tables\Columns\TextColumn::make('subject_type')->label('Subject type')->searchable(),
                Tables\Columns\TextColumn::make('subject_external_id')->label('External ID')->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('scope_key')->label('Scope')->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('created_at')->since()->label('Created'),
            ])
            ->filters([
                Tables\Filters\SelectFilter::make('status')
                    ->options([
                        Finding::STATUS_NEW => 'New',
                        Finding::STATUS_ACKNOWLEDGED => 'Acknowledged',
                    ])
                    ->default(Finding::STATUS_NEW),
                Tables\Filters\SelectFilter::make('finding_type')
                    ->options([
                        Finding::FINDING_TYPE_DRIFT => 'Drift',
                    ])
                    ->default(Finding::FINDING_TYPE_DRIFT),
                Tables\Filters\Filter::make('scope_key')
                    ->form([
                        TextInput::make('scope_key')
                            ->label('Scope key')
                            ->placeholder('Inventory selection hash')
                            ->maxLength(255),
                    ])
                    ->query(function (Builder $query, array $data): Builder {
                        $scopeKey = $data['scope_key'] ?? null;

                        if (! is_string($scopeKey) || $scopeKey === '') {
                            return $query;
                        }

                        return $query->where('scope_key', $scopeKey);
                    }),
                Tables\Filters\Filter::make('run_ids')
                    ->label('Run IDs')
                    ->form([
                        TextInput::make('baseline_run_id')
                            ->label('Baseline run id')
                            ->numeric(),
                        TextInput::make('current_run_id')
                            ->label('Current run id')
                            ->numeric(),
                    ])
                    ->query(function (Builder $query, array $data): Builder {
                        $baselineRunId = $data['baseline_run_id'] ?? null;
                        if (is_numeric($baselineRunId)) {
                            $query->where('baseline_run_id', (int) $baselineRunId);
                        }

                        $currentRunId = $data['current_run_id'] ?? null;
                        if (is_numeric($currentRunId)) {
                            $query->where('current_run_id', (int) $currentRunId);
                        }

                        return $query;
                    }),
            ])
            ->actions([
                Actions\Action::make('acknowledge')
                    ->label('Acknowledge')
                    ->icon('heroicon-o-check')
                    ->color('gray')
                    ->requiresConfirmation()
                    ->visible(fn (Finding $record): bool => $record->status === Finding::STATUS_NEW)
                    ->authorize(function (Finding $record): bool {
                        $user = auth()->user();

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

                        return $user->can('update', $record);
                    })
                    ->action(function (Finding $record): void {
                        $tenant = Tenant::current();
                        $user = auth()->user();

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

                        if ((int) $record->tenant_id !== (int) $tenant->getKey()) {
                            Notification::make()
                                ->title('Finding belongs to a different tenant')
                                ->danger()
                                ->send();

                            return;
                        }

                        $record->acknowledge($user);

                        Notification::make()
                            ->title('Finding acknowledged')
                            ->success()
                            ->send();
                    }),
                Actions\ViewAction::make(),
            ])
            ->bulkActions([
                BulkActionGroup::make([
                    BulkAction::make('acknowledge_selected')
                        ->label('Acknowledge selected')
                        ->icon('heroicon-o-check')
                        ->color('gray')
                        ->authorize(function (): bool {
                            $tenant = Tenant::current();
                            $user = auth()->user();

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

                            $probe = new Finding(['tenant_id' => $tenant->getKey()]);

                            return $user->can('update', $probe);
                        })
                        ->authorizeIndividualRecords('update')
                        ->requiresConfirmation()
                        ->action(function (Collection $records): void {
                            $tenant = Tenant::current();
                            $user = auth()->user();

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

                            $firstRecord = $records->first();
                            if ($firstRecord instanceof Finding) {
                                Gate::authorize('update', $firstRecord);
                            }

                            $acknowledgedCount = 0;
                            $skippedCount = 0;

                            foreach ($records as $record) {
                                if (! $record instanceof Finding) {
                                    $skippedCount++;

                                    continue;
                                }

                                if ((int) $record->tenant_id !== (int) $tenant->getKey()) {
                                    $skippedCount++;

                                    continue;
                                }

                                if ($record->status !== Finding::STATUS_NEW) {
                                    $skippedCount++;

                                    continue;
                                }

                                $record->acknowledge($user);
                                $acknowledgedCount++;
                            }

                            $body = "Acknowledged {$acknowledgedCount} finding".($acknowledgedCount === 1 ? '' : 's').'.';
                            if ($skippedCount > 0) {
                                $body .= " Skipped {$skippedCount}.";
                            }

                            Notification::make()
                                ->title('Bulk acknowledge completed')
                                ->body($body)
                                ->success()
                                ->send();
                        })
                        ->deselectRecordsAfterCompletion(),
                ]),
            ]);
    }

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

        return parent::getEloquentQuery()
            ->addSelect([
                'subject_display_name' => InventoryItem::query()
                    ->select('display_name')
                    ->whereColumn('inventory_items.tenant_id', 'findings.tenant_id')
                    ->whereColumn('inventory_items.external_id', 'findings.subject_external_id')
                    ->limit(1),
            ])
            ->when($tenantId, fn (Builder $query) => $query->where('tenant_id', $tenantId));
    }

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