<?php

declare(strict_types=1);

namespace App\Filament\Pages\Findings;

use App\Filament\Resources\FindingExceptionResource;
use App\Filament\Resources\FindingResource;
use App\Models\Finding;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Services\Findings\FindingWorkflowService;
use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Filament\CanonicalAdminEnvironmentFilterState;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\OperateHub\OperateHubShell;
use App\Support\Rbac\UiEnforcement;
use App\Support\Rbac\UiTooltips;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceType;
use App\Support\Workspaces\WorkspaceContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use UnitEnum;

class FindingsIntakeQueue extends Page implements HasTable
{
    use InteractsWithTable;

    protected static bool $isDiscovered = false;

    protected static bool $shouldRegisterNavigation = false;

    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-inbox-stack';

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

    protected static ?string $title = 'Findings intake';

    protected static ?string $slug = 'findings/intake';

    protected string $view = 'filament.pages.findings.findings-intake-queue';

    /**
     * @var array<int, ManagedEnvironment>|null
     */
    private ?array $authorizedTenants = null;

    /**
     * @var array<int, ManagedEnvironment>|null
     */
    private ?array $visibleTenants = null;

    private ?Workspace $workspace = null;

    public string $queueView = 'unassigned';

    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport)
            ->withListRowPrimaryActionLimit(1)
            ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the shared-unassigned scope fixed and expose only a tenant-prefilter clear action when needed.')
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
            ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The intake queue keeps Claim finding inline and does not render a secondary More menu on rows.')
            ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The intake queue does not expose bulk actions in v1.')
            ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty state stays calm and offers exactly one recovery CTA per branch.')
            ->exempt(ActionSurfaceSlot::DetailHeader, 'Row navigation returns to the existing tenant finding detail page while Claim finding remains the only inline safe shortcut.');
    }

    public function mount(): void
    {
        $this->queueView = $this->resolveRequestedQueueView();
        $this->authorizePageAccess();

        app(CanonicalAdminEnvironmentFilterState::class)->sync(
            $this->getTableFiltersSessionKey(),
            [],
            request(),
        );

        $this->applyRequestedTenantPrefilter();
        $this->mountInteractsWithTable();
        $this->normalizeTenantFilterState();
    }

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

        $governanceContext = $this->incomingGovernanceContext();

        if ($governanceContext?->backLinkUrl !== null) {
            $actions[] = Action::make('return_to_governance_inbox')
                ->label($governanceContext->backLinkLabel ?? 'Back to governance inbox')
                ->icon('heroicon-o-arrow-left')
                ->color('gray')
                ->url($governanceContext->backLinkUrl);
        }

        $actions[] = Action::make('clear_tenant_filter')
            ->label('Clear environment filter')
            ->icon('heroicon-o-x-mark')
            ->color('gray')
            ->visible(fn (): bool => $this->currentTenantFilterId() !== null)
            ->action(fn (): mixed => $this->clearTenantFilter());

        return $actions;
    }

    public function table(Table $table): Table
    {
        return $table
            ->query(fn (): Builder => $this->queueViewQuery())
            ->paginated(TablePaginationProfiles::customPage())
            ->persistFiltersInSession()
            ->columns([
                TextColumn::make('tenant.name')
                    ->label('ManagedEnvironment'),
                TextColumn::make('subject_display_name')
                    ->label('Finding')
                    ->state(fn (Finding $record): string => $record->resolvedSubjectDisplayName() ?? 'Finding #'.$record->getKey())
                    ->description(fn (Finding $record): ?string => $this->ownerContext($record))
                    ->wrap(),
                TextColumn::make('severity')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingSeverity))
                    ->color(BadgeRenderer::color(BadgeDomain::FindingSeverity))
                    ->icon(BadgeRenderer::icon(BadgeDomain::FindingSeverity))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingSeverity)),
                TextColumn::make('status')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingStatus))
                    ->color(BadgeRenderer::color(BadgeDomain::FindingStatus))
                    ->icon(BadgeRenderer::icon(BadgeDomain::FindingStatus))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingStatus))
                    ->description(fn (Finding $record): ?string => $this->reopenedCue($record)),
                TextColumn::make('due_at')
                    ->label('Due')
                    ->dateTime()
                    ->placeholder('—')
                    ->description(fn (Finding $record): ?string => FindingExceptionResource::relativeTimeDescription($record->due_at) ?? FindingResource::dueAttentionLabelFor($record)),
                TextColumn::make('intake_reason')
                    ->label('Queue reason')
                    ->badge()
                    ->state(fn (Finding $record): string => $this->queueReason($record))
                    ->color(fn (Finding $record): string => $this->queueReasonColor($record)),
            ])
            ->filters([
                SelectFilter::make('managed_environment_id')
                    ->label('ManagedEnvironment')
                    ->options(fn (): array => $this->tenantFilterOptions())
                    ->searchable(),
            ])
            ->actions([
                $this->claimAction(),
            ])
            ->bulkActions([])
            ->recordUrl(fn (Finding $record): string => $this->findingDetailUrl($record))
            ->emptyStateHeading(fn (): string => $this->emptyState()['title'])
            ->emptyStateDescription(fn (): string => $this->emptyState()['body'])
            ->emptyStateIcon(fn (): string => $this->emptyState()['icon'])
            ->emptyStateActions($this->emptyStateActions());
    }

    /**
     * @return array<string, mixed>
     */
    public function appliedScope(): array
    {
        $tenant = $this->filteredTenant();
        $queueView = $this->currentQueueView();

        return [
            'workspace_scoped' => true,
            'fixed_scope' => 'visible_unassigned_open_findings_only',
            'queue_view' => $queueView,
            'queue_view_label' => $this->queueViewLabel($queueView),
            'tenant_prefilter_source' => $this->tenantPrefilterSource(),
            'tenant_label' => $tenant?->name,
        ];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function queueViews(): array
    {
        $queueView = $this->currentQueueView();

        return [
            [
                'key' => 'unassigned',
                'label' => 'Unassigned',
                'fixed' => true,
                'active' => $queueView === 'unassigned',
                'badge_count' => (clone $this->filteredQueueQuery(queueView: 'unassigned', applyOrdering: false))->count(),
                'url' => $this->queueUrl(['view' => null]),
            ],
            [
                'key' => 'needs_triage',
                'label' => 'Needs triage',
                'fixed' => true,
                'active' => $queueView === 'needs_triage',
                'badge_count' => (clone $this->filteredQueueQuery(queueView: 'needs_triage', applyOrdering: false))->count(),
                'url' => $this->queueUrl(['view' => 'needs_triage']),
            ],
        ];
    }

    /**
     * @return array{visible_unassigned: int, visible_needs_triage: int, visible_overdue: int}
     */
    public function summaryCounts(): array
    {
        $visibleQuery = $this->filteredQueueQuery(queueView: 'unassigned', applyOrdering: false);

        return [
            'visible_unassigned' => (clone $visibleQuery)->count(),
            'visible_needs_triage' => (clone $this->filteredQueueQuery(queueView: 'needs_triage', applyOrdering: false))->count(),
            'visible_overdue' => (clone $visibleQuery)
                ->whereNotNull('due_at')
                ->where('due_at', '<', now())
                ->count(),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function emptyState(): array
    {
        if ($this->tenantFilterAloneExcludesRows()) {
            return [
                'title' => 'No intake findings match this environment scope',
                'body' => 'Your current environment filter is hiding shared intake work that is still visible elsewhere in this workspace.',
                'icon' => 'heroicon-o-funnel',
                'action_name' => 'clear_tenant_filter_empty',
                'action_label' => 'Clear environment filter',
                'action_kind' => 'clear_tenant_filter',
            ];
        }

        return [
            'title' => 'Shared intake is clear',
            'body' => 'No visible unassigned findings currently need first routing across your entitled environments. Open your personal queue if you want to continue with claimed work.',
            'icon' => 'heroicon-o-inbox-stack',
            'action_name' => 'open_my_findings_empty',
            'action_label' => 'Open my findings',
            'action_kind' => 'url',
            'action_url' => MyFindingsInbox::getUrl(panel: 'admin'),
        ];
    }

    public function updatedTableFilters(): void
    {
        $this->normalizeTenantFilterState();
    }

    public function clearTenantFilter(): void
    {
        $this->removeTableFilter('managed_environment_id');
        $this->resetTable();
    }

    /**
     * @return array<int, ManagedEnvironment>
     */
    public function visibleTenants(): array
    {
        if ($this->visibleTenants !== null) {
            return $this->visibleTenants;
        }

        $user = auth()->user();
        $tenants = $this->authorizedTenants();

        if (! $user instanceof User || $tenants === []) {
            return $this->visibleTenants = [];
        }

        $resolver = app(CapabilityResolver::class);
        $resolver->primeMemberships(
            $user,
            array_map(static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey(), $tenants),
        );

        return $this->visibleTenants = array_values(array_filter(
            $tenants,
            fn (ManagedEnvironment $tenant): bool => $resolver->can($user, $tenant, Capabilities::TENANT_FINDINGS_VIEW),
        ));
    }

    private function claimAction(): Action
    {
        return UiEnforcement::forTableAction(
            Action::make('claim')
                ->label('Claim finding')
                ->icon('heroicon-o-user-plus')
                ->color('gray')
                ->visible(fn (Finding $record): bool => $record->assignee_user_id === null && in_array((string) $record->status, Finding::openStatuses(), true))
                ->requiresConfirmation()
                ->modalHeading('Claim finding')
                ->modalDescription(function (?Finding $record = null): string {
                    $findingLabel = $record?->resolvedSubjectDisplayName() ?? ($record instanceof Finding ? 'Finding #'.$record->getKey() : 'this finding');
                    $tenantLabel = $record?->tenant?->name ?? 'this tenant';

                    return sprintf(
                        'Claim "%s" in %s? It will move into your personal queue, while the accountable owner and lifecycle state stay unchanged.',
                        $findingLabel,
                        $tenantLabel,
                    );
                })
                ->modalSubmitActionLabel('Claim finding')
                ->action(function (Finding $record): void {
                    $tenant = $record->tenant;
                    $user = auth()->user();

                    if (! $tenant instanceof ManagedEnvironment) {
                        throw new NotFoundHttpException;
                    }

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

                    try {
                        $claimedFinding = app(FindingWorkflowService::class)->claim($record, $tenant, $user);

                        Notification::make()
                            ->success()
                            ->title('Finding claimed')
                            ->body('The finding left shared intake and is now assigned to you.')
                            ->actions([
                                Action::make('open_my_findings')
                                    ->label('Open my findings')
                                    ->url(MyFindingsInbox::getUrl(panel: 'admin')),
                                Action::make('open_finding')
                                    ->label('Open finding')
                                    ->url($this->findingDetailUrl($claimedFinding)),
                            ])
                            ->send();
                    } catch (ConflictHttpException) {
                        Notification::make()
                            ->warning()
                            ->title('Finding already claimed')
                            ->body('Another operator claimed this finding first. The intake queue has been refreshed.')
                            ->send();
                    }

                    $this->resetTable();

                    if (method_exists($this, 'unmountAction')) {
                        $this->unmountAction();
                    }
                }),
            fn () => null,
        )
            ->preserveVisibility()
            ->requireCapability(Capabilities::TENANT_FINDINGS_ASSIGN)
            ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
            ->apply();
    }

    private function authorizePageAccess(): void
    {
        $user = auth()->user();
        $workspace = $this->workspace();

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

        if (! $workspace instanceof Workspace) {
            throw new NotFoundHttpException;
        }

        $resolver = app(WorkspaceCapabilityResolver::class);

        if (! $resolver->isMember($user, $workspace)) {
            throw new NotFoundHttpException;
        }

        if ($this->visibleTenants() === []) {
            abort(403);
        }
    }

    /**
     * @return array<int, ManagedEnvironment>
     */
    private function authorizedTenants(): array
    {
        if ($this->authorizedTenants !== null) {
            return $this->authorizedTenants;
        }

        $user = auth()->user();
        $workspace = $this->workspace();

        if (! $user instanceof User || ! $workspace instanceof Workspace) {
            return $this->authorizedTenants = [];
        }

        return $this->authorizedTenants = $user->accessibleManagedEnvironmentsQuery((int) $workspace->getKey())
            ->where('managed_environments.lifecycle_status', 'active')
            ->orderBy('managed_environments.name')
            ->get(['managed_environments.id', 'managed_environments.name', 'managed_environments.slug', 'managed_environments.workspace_id'])
            ->all();
    }

    private function workspace(): ?Workspace
    {
        if ($this->workspace instanceof Workspace) {
            return $this->workspace;
        }

        $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());

        if (! is_int($workspaceId)) {
            return null;
        }

        return $this->workspace = Workspace::query()->whereKey($workspaceId)->first();
    }

    private function queueBaseQuery(): Builder
    {
        $workspace = $this->workspace();
        $tenantIds = array_map(
            static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey(),
            $this->visibleTenants(),
        );

        if (! $workspace instanceof Workspace) {
            return Finding::query()->whereRaw('1 = 0');
        }

        return Finding::query()
            ->with(['tenant', 'ownerUser', 'assigneeUser'])
            ->withSubjectDisplayName()
            ->where('workspace_id', (int) $workspace->getKey())
            ->whereIn('managed_environment_id', $tenantIds === [] ? [-1] : $tenantIds)
            ->whereNull('assignee_user_id')
            ->whereIn('status', Finding::openStatuses());
    }

    private function queueViewQuery(): Builder
    {
        return $this->filteredQueueQuery(includeTenantFilter: false, queueView: $this->currentQueueView(), applyOrdering: true);
    }

    private function filteredQueueQuery(
        bool $includeTenantFilter = true,
        ?string $queueView = null,
        bool $applyOrdering = true,
    ): Builder {
        $query = $this->queueBaseQuery();
        $resolvedQueueView = $queueView ?? $this->queueView;

        if ($includeTenantFilter && ($tenantId = $this->currentTenantFilterId()) !== null) {
            $query->where('managed_environment_id', $tenantId);
        }

        if ($resolvedQueueView === 'needs_triage') {
            $query->whereIn('status', [
                Finding::STATUS_NEW,
                Finding::STATUS_REOPENED,
            ]);
        }

        if (! $applyOrdering) {
            return $query;
        }

        return $query
            ->orderByRaw(
                'case
                    when due_at is not null and due_at < ? then 0
                    when status = ? then 1
                    when status = ? then 2
                    else 3
                end asc',
                [now(), Finding::STATUS_REOPENED, Finding::STATUS_NEW],
            )
            ->orderByRaw('case when due_at is null then 1 else 0 end asc')
            ->orderBy('due_at')
            ->orderByDesc('id');
    }

    /**
     * @return array<string, string>
     */
    private function tenantFilterOptions(): array
    {
        return collect($this->visibleTenants())
            ->mapWithKeys(static fn (ManagedEnvironment $tenant): array => [
                (string) $tenant->getKey() => (string) $tenant->name,
            ])
            ->all();
    }

    private function applyRequestedTenantPrefilter(): void
    {
        $requestedTenant = request()->query('tenant');

        if (! is_string($requestedTenant) && ! is_numeric($requestedTenant)) {
            return;
        }

        foreach ($this->visibleTenants() as $tenant) {
            if ((string) $tenant->getKey() !== (string) $requestedTenant && (string) $tenant->external_id !== (string) $requestedTenant) {
                continue;
            }

            $this->tableFilters['managed_environment_id']['value'] = (string) $tenant->getKey();
            $this->tableDeferredFilters['managed_environment_id']['value'] = (string) $tenant->getKey();

            return;
        }
    }

    private function normalizeTenantFilterState(): void
    {
        $configuredTenantFilter = data_get($this->currentQueueFiltersState(), 'managed_environment_id.value');

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

        if ($this->currentTenantFilterId() !== null) {
            return;
        }

        $this->removeTableFilter('managed_environment_id');
    }

    /**
     * @return array<string, mixed>
     */
    private function currentQueueFiltersState(): array
    {
        $persisted = session()->get($this->getTableFiltersSessionKey(), []);

        return array_replace_recursive(
            is_array($persisted) ? $persisted : [],
            $this->tableFilters ?? [],
        );
    }

    private function currentTenantFilterId(): ?int
    {
        $tenantFilter = data_get($this->currentQueueFiltersState(), 'managed_environment_id.value');

        if (! is_numeric($tenantFilter)) {
            return null;
        }

        $tenantId = (int) $tenantFilter;

        foreach ($this->visibleTenants() as $tenant) {
            if ((int) $tenant->getKey() === $tenantId) {
                return $tenantId;
            }
        }

        return null;
    }

    private function filteredTenant(): ?ManagedEnvironment
    {
        $tenantId = $this->currentTenantFilterId();

        if (! is_int($tenantId)) {
            return null;
        }

        foreach ($this->visibleTenants() as $tenant) {
            if ((int) $tenant->getKey() === $tenantId) {
                return $tenant;
            }
        }

        return null;
    }

    private function activeVisibleTenant(): ?ManagedEnvironment
    {
        $activeEnvironment = app(OperateHubShell::class)->activeEntitledTenant(request());

        if (! $activeEnvironment instanceof ManagedEnvironment) {
            return null;
        }

        foreach ($this->visibleTenants() as $tenant) {
            if ($tenant->is($activeEnvironment)) {
                return $tenant;
            }
        }

        return null;
    }

    private function tenantPrefilterSource(): string
    {
        $tenant = $this->filteredTenant();

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

        $activeEnvironment = $this->activeVisibleTenant();

        if ($activeEnvironment instanceof ManagedEnvironment && $activeEnvironment->is($tenant)) {
            return 'active_tenant_context';
        }

        return 'explicit_filter';
    }

    private function ownerContext(Finding $record): ?string
    {
        if ($record->owner_user_id === null) {
            return null;
        }

        return 'Owner: '.FindingResource::accountableOwnerDisplayFor($record);
    }

    private function reopenedCue(Finding $record): ?string
    {
        if ($record->reopened_at === null) {
            return null;
        }

        return 'Reopened';
    }

    private function queueReason(Finding $record): string
    {
        return in_array((string) $record->status, [
            Finding::STATUS_NEW,
            Finding::STATUS_REOPENED,
        ], true)
            ? 'Needs triage'
            : 'Unassigned';
    }

    private function queueReasonColor(Finding $record): string
    {
        return $this->queueReason($record) === 'Needs triage'
            ? 'warning'
            : 'gray';
    }

    private function tenantFilterAloneExcludesRows(): bool
    {
        if ($this->currentTenantFilterId() === null) {
            return false;
        }

        if ((clone $this->filteredQueueQuery())->exists()) {
            return false;
        }

        return (clone $this->filteredQueueQuery(includeTenantFilter: false))->exists();
    }

    private function findingDetailUrl(Finding $record): string
    {
        $tenant = $record->tenant;

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

        $url = FindingResource::getUrl('view', ['record' => $record], tenant: $tenant);

        return $this->appendQuery($url, $this->navigationContext()->toQuery());
    }

    private function navigationContext(): CanonicalNavigationContext
    {
        return new CanonicalNavigationContext(
            sourceSurface: 'findings.intake',
            canonicalRouteName: static::getRouteName(Filament::getPanel('admin')),
            tenantId: $this->currentTenantFilterId(),
            backLinkLabel: 'Back to findings intake',
            backLinkUrl: $this->queueUrl(),
        );
    }

    private function incomingGovernanceContext(): ?CanonicalNavigationContext
    {
        $context = CanonicalNavigationContext::fromRequest(request());

        return $context?->sourceSurface === 'governance.inbox'
            ? $context
            : null;
    }

    private function queueUrl(array $overrides = []): string
    {
        $resolvedTenant = array_key_exists('tenant', $overrides)
            ? $overrides['tenant']
            : $this->filteredTenant()?->external_id;
        $resolvedView = array_key_exists('view', $overrides)
            ? $overrides['view']
            : $this->currentQueueView();

        return static::getUrl(
            panel: 'admin',
            parameters: array_filter([
                'tenant' => is_string($resolvedTenant) && $resolvedTenant !== '' ? $resolvedTenant : null,
                'view' => $resolvedView === 'needs_triage' ? 'needs_triage' : null,
            ], static fn (mixed $value): bool => $value !== null && $value !== ''),
        );
    }

    private function resolveRequestedQueueView(): string
    {
        $requestedView = request()->query('view');

        return $requestedView === 'needs_triage'
            ? 'needs_triage'
            : 'unassigned';
    }

    private function currentQueueView(): string
    {
        return $this->queueView === 'needs_triage'
            ? 'needs_triage'
            : 'unassigned';
    }

    private function queueViewLabel(string $queueView): string
    {
        return $queueView === 'needs_triage'
            ? 'Needs triage'
            : 'Unassigned';
    }

    /**
     * @return array<int, Action>
     */
    private function emptyStateActions(): array
    {
        $emptyState = $this->emptyState();
        $action = Action::make((string) $emptyState['action_name'])
            ->label((string) $emptyState['action_label'])
            ->icon('heroicon-o-arrow-right')
            ->color('gray');

        if (($emptyState['action_kind'] ?? null) === 'clear_tenant_filter') {
            return [
                $action->action(fn (): mixed => $this->clearTenantFilter()),
            ];
        }

        return [
            $action->url((string) $emptyState['action_url']),
        ];
    }

    /**
     * @param  array<string, mixed>  $query
     */
    private function appendQuery(string $url, array $query): string
    {
        if ($query === []) {
            return $url;
        }

        return $url.(str_contains($url, '?') ? '&' : '?').http_build_query($query);
    }
}
