<?php

namespace App\Filament\Resources\FindingResource\Pages;

use App\Filament\Concerns\ResolvesPanelTenantContext;
use App\Filament\Resources\FindingResource;
use App\Filament\Widgets\ManagedEnvironment\BaselineCompareCoverageBanner;
use App\Filament\Widgets\ManagedEnvironment\FindingStatsOverview;
use App\Models\Finding;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Findings\FindingWorkflowService;
use App\Support\Auth\Capabilities;
use App\Support\Filament\CanonicalAdminEnvironmentFilterState;
use App\Support\Rbac\UiEnforcement;
use App\Support\Rbac\UiTooltips;
use Filament\Actions;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ListRecords;
use Filament\Schemas\Components\Tabs\Tab;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Arr;
use Throwable;

class ListFindings extends ListRecords
{
    use ResolvesPanelTenantContext;

    protected static string $resource = FindingResource::class;

    /**
     * @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 && filled($context['recordKey'] ?? null) && in_array($name, ['triage', 'start_progress', 'assign', 'resolve', 'close', 'request_exception', 'reopen'], true)) {
            try {
                FindingResource::resolveScopedRecordOrFail($context['recordKey']);
            } catch (ModelNotFoundException) {
                abort(404);
            }
        }

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

    public function mount(): void
    {
        $this->syncCanonicalAdminEnvironmentFilterState();

        parent::mount();
        $this->applyRequestedDashboardPrefilter();
    }

    protected function getHeaderWidgets(): array
    {
        return [
            BaselineCompareCoverageBanner::class,
            FindingStatsOverview::class,
        ];
    }

    /**
     * @return array<string, Tab>
     */
    public function getTabs(): array
    {
        $stats = FindingResource::findingStatsForCurrentTenant();

        return [
            'all' => Tab::make(__('localization.findings.all'))
                ->icon('heroicon-m-list-bullet'),
            'needs_action' => Tab::make(__('localization.findings.needs_action'))
                ->icon('heroicon-m-exclamation-triangle')
                ->modifyQueryUsing(fn (Builder $query): Builder => $query
                    ->whereIn('status', Finding::openStatusesForQuery()))
                ->badge($stats['open'] > 0 ? $stats['open'] : null)
                ->badgeColor('warning'),
            'overdue' => Tab::make(__('localization.findings.overdue'))
                ->icon('heroicon-m-clock')
                ->modifyQueryUsing(fn (Builder $query): Builder => $query
                    ->whereIn('status', Finding::openStatusesForQuery())
                    ->whereNotNull('due_at')
                    ->where('due_at', '<', now()))
                ->badge($stats['overdue'] > 0 ? $stats['overdue'] : null)
                ->badgeColor('danger'),
            'risk_accepted' => Tab::make(__('localization.findings.risk_accepted'))
                ->icon('heroicon-m-shield-check')
                ->modifyQueryUsing(fn (Builder $query): Builder => $query
                    ->where('status', Finding::STATUS_RISK_ACCEPTED)),
            'resolved' => Tab::make(__('localization.findings.resolved'))
                ->icon('heroicon-m-archive-box')
                ->modifyQueryUsing(fn (Builder $query): Builder => $query
                    ->whereIn('status', [Finding::STATUS_RESOLVED, Finding::STATUS_CLOSED])),
        ];
    }

    protected function getHeaderActions(): array
    {
        $actions = [];

        $actions[] = UiEnforcement::forAction(
            Actions\Action::make('triage_all_matching')
                ->label('Triage all matching')
                ->icon('heroicon-o-check')
                ->color('gray')
                ->requiresConfirmation()
                ->visible(fn (): bool => $this->getStatusFilterValue() === Finding::STATUS_NEW)
                ->modalDescription(function (): string {
                    $count = $this->getAllMatchingCount();

                    return "You are about to triage {$count} finding".($count === 1 ? '' : 's').' matching the current filters.';
                })
                ->form(function (): array {
                    $count = $this->getAllMatchingCount();

                    if ($count <= 100) {
                        return [];
                    }

                    return [
                        TextInput::make('confirmation')
                            ->label('Type TRIAGE to confirm')
                            ->required()
                            ->in(['TRIAGE'])
                            ->validationMessages([
                                'in' => 'Please type TRIAGE to confirm.',
                            ]),
                    ];
                })
                ->action(function (FindingWorkflowService $workflow): void {
                    $query = $this->buildAllMatchingQuery();
                    $count = (clone $query)->count();

                    if ($count === 0) {
                        Notification::make()
                            ->title('No matching findings')
                            ->body('There are no new findings matching the current filters to triage.')
                            ->warning()
                            ->send();

                        return;
                    }

                    $user = auth()->user();
                    $tenant = static::resolveTenantContextForCurrentPanel();

                    if (! $user instanceof User) {
                        abort(403);
                    }

                    if (! $tenant instanceof ManagedEnvironment) {
                        abort(404);
                    }

                    $triagedCount = 0;
                    $skippedCount = 0;
                    $failedCount = 0;

                    $query->orderBy('id')->chunkById(200, function ($findings) use ($workflow, $tenant, $user, &$triagedCount, &$skippedCount, &$failedCount): void {
                        foreach ($findings as $finding) {
                            if (! $finding instanceof Finding) {
                                $skippedCount++;

                                continue;
                            }

                            if (! in_array((string) $finding->status, [
                                Finding::STATUS_NEW,
                                Finding::STATUS_REOPENED,
                            ], true)) {
                                $skippedCount++;

                                continue;
                            }

                            try {
                                $workflow->triage($finding, $tenant, $user);
                                $triagedCount++;
                            } catch (Throwable) {
                                $failedCount++;
                            }
                        }
                    });

                    $this->deselectAllTableRecords();
                    $this->resetPage();

                    $body = "Triaged {$triagedCount} finding".($triagedCount === 1 ? '' : 's').'.';
                    if ($skippedCount > 0) {
                        $body .= " Skipped {$skippedCount}.";
                    }
                    if ($failedCount > 0) {
                        $body .= " Failed {$failedCount}.";
                    }

                    Notification::make()
                        ->title('Bulk triage completed')
                        ->body($body)
                        ->status($failedCount > 0 ? 'warning' : 'success')
                        ->send();
                })
        )
            ->preserveVisibility()
            ->requireCapability(Capabilities::TENANT_FINDINGS_TRIAGE)
            ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
            ->apply();

        return $actions;
    }

    protected function buildAllMatchingQuery(): Builder
    {
        $query = FindingResource::getEloquentQuery();

        $query->where('status', Finding::STATUS_NEW);

        $findingType = $this->getFindingTypeFilterValue();
        if (is_string($findingType) && $findingType !== '') {
            $query->where('finding_type', $findingType);
        }

        if ($this->filterIsActive('overdue')) {
            $query->whereNotNull('due_at')->where('due_at', '<', now());
        }

        if ($this->filterIsActive('high_severity')) {
            $query->whereIn('severity', [
                Finding::SEVERITY_HIGH,
                Finding::SEVERITY_CRITICAL,
            ]);
        }

        if ($this->filterIsActive('my_assigned')) {
            $userId = auth()->id();

            if (is_numeric($userId)) {
                $query->where('assignee_user_id', (int) $userId);
            } else {
                $query->whereRaw('1 = 0');
            }
        }

        $scopeKeyState = $this->getTableFilterState('scope_key') ?? [];
        $scopeKey = Arr::get($scopeKeyState, 'scope_key');
        if (is_string($scopeKey) && $scopeKey !== '') {
            $query->where('scope_key', $scopeKey);
        }

        $runIdsState = $this->getTableFilterState('run_ids') ?? [];
        $baselineRunId = Arr::get($runIdsState, 'baseline_operation_run_id');
        if (is_numeric($baselineRunId)) {
            $query->where('baseline_operation_run_id', (int) $baselineRunId);
        }

        $currentRunId = Arr::get($runIdsState, 'current_operation_run_id');
        if (is_numeric($currentRunId)) {
            $query->where('current_operation_run_id', (int) $currentRunId);
        }

        return $query;
    }

    private function syncCanonicalAdminEnvironmentFilterState(): void
    {
        app(CanonicalAdminEnvironmentFilterState::class)->sync(
            $this->getTableFiltersSessionKey(),
            environmentSensitiveFilters: ['scope_key', 'run_ids'],
            request: request(),
            environmentFilterName: null,
        );
    }

    private function applyRequestedDashboardPrefilter(): void
    {
        $requestedTab = request()->query('tab');
        $requestedStatus = request()->query('status');
        $requestedFindingType = request()->query('finding_type');
        $requestedGovernanceValidity = request()->query('governance_validity');
        $requestedHighSeverity = request()->query('high_severity');

        $hasDashboardPrefilter = $requestedTab !== null
            || $requestedStatus !== null
            || $requestedFindingType !== null
            || $requestedGovernanceValidity !== null
            || $requestedHighSeverity !== null;

        if (! $hasDashboardPrefilter) {
            return;
        }

        foreach (['status', 'finding_type', 'workflow_family', 'governance_validity'] as $filterName) {
            data_forget($this->tableFilters, $filterName);
            data_forget($this->tableDeferredFilters, $filterName);
        }

        foreach (['high_severity', 'overdue', 'my_assigned'] as $filterName) {
            data_forget($this->tableFilters, "{$filterName}.isActive");
            data_forget($this->tableDeferredFilters, "{$filterName}.isActive");
        }

        if (in_array($requestedTab, array_keys($this->getTabs()), true)) {
            $this->activeTab = (string) $requestedTab;
        }

        if (is_string($requestedStatus) && $requestedStatus !== '') {
            $this->tableFilters['status']['value'] = $requestedStatus;
            $this->tableDeferredFilters['status']['value'] = $requestedStatus;
        }

        if (is_string($requestedFindingType) && $requestedFindingType !== '') {
            $this->tableFilters['finding_type']['value'] = $requestedFindingType;
            $this->tableDeferredFilters['finding_type']['value'] = $requestedFindingType;
        }

        if (is_string($requestedGovernanceValidity) && $requestedGovernanceValidity !== '') {
            $this->tableFilters['governance_validity']['value'] = $requestedGovernanceValidity;
            $this->tableDeferredFilters['governance_validity']['value'] = $requestedGovernanceValidity;
        }

        $highSeverity = filter_var($requestedHighSeverity, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE);

        if ($highSeverity === true) {
            $this->tableFilters['high_severity']['isActive'] = true;
            $this->tableDeferredFilters['high_severity']['isActive'] = true;
        }
    }

    private function filterIsActive(string $filterName): bool
    {
        $state = $this->getTableFilterState($filterName);

        if ($state === true) {
            return true;
        }

        if (is_array($state)) {
            $isActive = Arr::get($state, 'isActive');

            return $isActive === true;
        }

        return false;
    }

    protected function getAllMatchingCount(): int
    {
        return (int) $this->buildAllMatchingQuery()->count();
    }

    protected function getStatusFilterValue(): string
    {
        $state = $this->getTableFilterState('status') ?? [];
        $value = Arr::get($state, 'value');

        return is_string($value) && $value !== ''
            ? $value
            : Finding::STATUS_NEW;
    }

    protected function getFindingTypeFilterValue(): ?string
    {
        $state = $this->getTableFilterState('finding_type') ?? [];
        $value = Arr::get($state, 'value');

        return is_string($value) && $value !== ''
            ? $value
            : null;
    }
}
