<?php

declare(strict_types=1);

namespace App\Filament\Resources;

use App\Filament\Concerns\InteractsWithTenantOwnedRecords;
use App\Filament\Concerns\ResolvesPanelTenantContext;
use App\Filament\Concerns\WorkspaceScopedEnvironmentRoutes;
use App\Filament\Resources\FindingExceptionResource\Pages;
use App\Models\FindingException;
use App\Models\FindingExceptionEvidenceReference;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Auth\ManagedEnvironmentAccessScopeResolver;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Services\Findings\FindingExceptionService;
use App\Services\Findings\FindingRiskGovernanceResolver;
use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Filament\FilterOptionCatalog;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\NavigationScope;
use App\Support\Navigation\RelatedContextEntry;
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 BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\RepeatableEntry;
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\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;
use UnitEnum;

class FindingExceptionResource extends Resource
{
    use InteractsWithTenantOwnedRecords;
    use ResolvesPanelTenantContext;
    use WorkspaceScopedEnvironmentRoutes;

    protected static ?string $model = FindingException::class;

    protected static ?string $tenantOwnershipRelationshipName = 'tenant';

    protected static bool $isGloballySearchable = false;

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

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

    protected static ?string $navigationLabel = 'Risk exceptions';

    protected static ?int $navigationSort = 60;

    public static function shouldRegisterNavigation(): bool
    {
        return NavigationScope::shouldRegisterEnvironmentNavigation()
            && parent::shouldRegisterNavigation();
    }

    public static function canViewAny(): bool
    {
        $tenant = static::resolveTenantContextForCurrentPanel();
        $user = auth()->user();

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

        if (! $user->canAccessTenant($tenant)) {
            return false;
        }

        return $user->can(Capabilities::FINDING_EXCEPTION_VIEW, $tenant);
    }

    public static function canView(Model $record): bool
    {
        $tenant = static::resolveTenantContextForCurrentPanel();
        $user = auth()->user();

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

        if (! $user->canAccessTenant($tenant) || ! $user->can(Capabilities::FINDING_EXCEPTION_VIEW, $tenant)) {
            return false;
        }

        return ! $record instanceof FindingException
            || ((int) $record->managed_environment_id === (int) $tenant->getKey() && (int) $record->workspace_id === (int) $tenant->workspace_id);
    }

    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView)
            ->satisfy(ActionSurfaceSlot::ListHeader, 'List header links back to findings where exception requests originate.')
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
            ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'v1 keeps exception mutations direct and avoids a More menu.')
            ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'Exception decisions require per-record review and intentionally omit bulk actions in v1.')
            ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty state explains that new requests start from finding detail.')
            ->satisfy(ActionSurfaceSlot::DetailHeader, 'Detail header exposes renewal and revocation only, while linked finding and approval-queue navigation move into contextual related context.');
    }

    public static function getEloquentQuery(): Builder
    {
        return static::getTenantOwnedEloquentQuery()
            ->with(static::relationshipsForView());
    }

    /**
     * @return array{active: int, expiring: int, expired: int, pending: int, total: int}
     */
    public static function exceptionStatsForCurrentTenant(): array
    {
        $tenant = static::resolveTenantContextForCurrentPanel();

        if (! $tenant instanceof ManagedEnvironment) {
            return ['active' => 0, 'expiring' => 0, 'expired' => 0, 'pending' => 0, 'total' => 0];
        }

        $counts = FindingException::query()
            ->where('managed_environment_id', (int) $tenant->getKey())
            ->where('workspace_id', (int) $tenant->workspace_id)
            ->selectRaw('count(*) as total')
            ->selectRaw("count(*) filter (where status = 'active') as active")
            ->selectRaw("count(*) filter (where status = 'expiring') as expiring")
            ->selectRaw("count(*) filter (where status = 'expired') as expired")
            ->selectRaw("count(*) filter (where status = 'pending') as pending")
            ->first();

        return [
            'active' => (int) ($counts?->active ?? 0),
            'expiring' => (int) ($counts?->expiring ?? 0),
            'expired' => (int) ($counts?->expired ?? 0),
            'pending' => (int) ($counts?->pending ?? 0),
            'total' => (int) ($counts?->total ?? 0),
        ];
    }

    public static function resolveScopedRecordOrFail(int|string|null $record): Model
    {
        return static::resolveTenantOwnedRecordOrFail($record, parent::getEloquentQuery()->with(static::relationshipsForView()));
    }

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

    public static function infolist(Schema $schema): Schema
    {
        return $schema->schema([
            Section::make('Exception')
                ->schema([
                    TextEntry::make('status')
                        ->badge()
                        ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingExceptionStatus))
                        ->color(BadgeRenderer::color(BadgeDomain::FindingExceptionStatus))
                        ->icon(BadgeRenderer::icon(BadgeDomain::FindingExceptionStatus))
                        ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingExceptionStatus)),
                    TextEntry::make('current_validity_state')
                        ->label('Validity')
                        ->badge()
                        ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingRiskGovernanceValidity))
                        ->color(BadgeRenderer::color(BadgeDomain::FindingRiskGovernanceValidity))
                        ->icon(BadgeRenderer::icon(BadgeDomain::FindingRiskGovernanceValidity))
                        ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingRiskGovernanceValidity)),
                    TextEntry::make('governance_warning')
                        ->label('Governance warning')
                        ->state(fn (FindingException $record): ?string => static::governanceWarning($record))
                        ->color(fn (FindingException $record): string => static::governanceWarningColor($record))
                        ->columnSpanFull()
                        ->visible(fn (FindingException $record): bool => static::governanceWarning($record) !== null),
                    TextEntry::make('tenant.name')->label('ManagedEnvironment'),
                    TextEntry::make('finding_summary')
                        ->label('Finding')
                        ->state(fn (FindingException $record): string => static::findingSummary($record)),
                    TextEntry::make('requester.name')->label('Requested by')->placeholder('—'),
                    TextEntry::make('owner.name')->label('Owner')->placeholder('—'),
                    TextEntry::make('approver.name')->label('Approved by')->placeholder('—'),
                    TextEntry::make('requested_at')->label('Requested')->dateTime()->placeholder('—'),
                    TextEntry::make('approved_at')->label('Approved')->dateTime()->placeholder('—'),
                    TextEntry::make('review_due_at')->label('Review due')->dateTime()->placeholder('—'),
                    TextEntry::make('effective_from')->label('Effective from')->dateTime()->placeholder('—'),
                    TextEntry::make('expires_at')->label('Expires')->dateTime()->placeholder('—'),
                    TextEntry::make('request_reason')->label('Request reason')->columnSpanFull(),
                    TextEntry::make('approval_reason')->label('Approval reason')->placeholder('—')->columnSpanFull(),
                    TextEntry::make('rejection_reason')->label('Rejection reason')->placeholder('—')->columnSpanFull(),
                ])
                ->columns(2),
            Section::make('Decision history')
                ->schema([
                    RepeatableEntry::make('decisions')
                        ->hiddenLabel()
                        ->schema([
                            TextEntry::make('decision_type')->label('Decision'),
                            TextEntry::make('actor.name')->label('Actor')->placeholder('—'),
                            TextEntry::make('decided_at')->label('Decided')->dateTime()->placeholder('—'),
                            TextEntry::make('reason')->label('Reason')->placeholder('—')->columnSpanFull(),
                        ])
                        ->columns(3),
                ]),
            Section::make('Related context')
                ->schema([
                    ViewEntry::make('related_context')
                        ->label('')
                        ->view('filament.infolists.entries.related-context')
                        ->state(fn (FindingException $record): array => static::relatedContextEntries($record))
                        ->columnSpanFull(),
                ])
                ->columnSpanFull(),
            Section::make('Evidence references')
                ->schema([
                    RepeatableEntry::make('evidenceReferences')
                        ->hiddenLabel()
                        ->schema([
                            TextEntry::make('label')->label('Label'),
                            TextEntry::make('source_type')->label('Source'),
                            TextEntry::make('source_id')->label('Source ID')->placeholder('—'),
                            TextEntry::make('source_fingerprint')->label('Fingerprint')->placeholder('—'),
                            TextEntry::make('measured_at')->label('Measured')->dateTime()->placeholder('—'),
                            TextEntry::make('summary_payload')
                                ->label('Summary')
                                ->state(function (FindingExceptionEvidenceReference $record): ?string {
                                    if ($record->summary_payload === [] || $record->summary_payload === null) {
                                        return null;
                                    }

                                    return json_encode($record->summary_payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?: null;
                                })
                                ->placeholder('—')
                                ->columnSpanFull(),
                        ])
                        ->columns(2),
                ])
                ->visible(fn (FindingException $record): bool => $record->evidenceReferences->isNotEmpty()),
        ]);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public static function relatedContextEntries(FindingException $record): array
    {
        $entries = [];

        if ($record->finding && $record->tenant instanceof ManagedEnvironment) {
            $entries[] = RelatedContextEntry::available(
                key: 'finding',
                label: 'Finding',
                value: static::findingSummary($record),
                secondaryValue: 'Return to the linked finding detail.',
                targetUrl: FindingResource::getUrl('view', ['record' => $record->finding], tenant: $record->tenant),
                targetKind: 'direct_record',
                priority: 10,
                actionLabel: 'Open finding',
                contextBadge: 'Governance',
            )->toArray();
        }

        if ($record->tenant instanceof ManagedEnvironment && static::canAccessApprovalQueueForTenant($record->tenant)) {
            $entries[] = RelatedContextEntry::available(
                key: 'approval_queue',
                label: 'Approval queue',
                value: 'Review pending exception requests',
                secondaryValue: 'Return to the queue for the rest of this tenant’s governance workload.',
                targetUrl: static::approvalQueueUrl($record->tenant),
                targetKind: 'canonical_page',
                priority: 20,
                actionLabel: 'Open approval queue',
                contextBadge: 'Queue',
            )->toArray();
        }

        return $entries;
    }

    public static function table(Table $table): Table
    {
        return $table
            ->defaultSort('requested_at', 'desc')
            ->paginated(TablePaginationProfiles::resource())
            ->persistFiltersInSession()
            ->persistSearchInSession()
            ->persistSortInSession()
            ->recordUrl(fn (FindingException $record): string => static::getUrl('view', ['record' => $record]))
            ->columns([
                Tables\Columns\TextColumn::make('status')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingExceptionStatus))
                    ->color(BadgeRenderer::color(BadgeDomain::FindingExceptionStatus))
                    ->icon(BadgeRenderer::icon(BadgeDomain::FindingExceptionStatus))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingExceptionStatus))
                    ->sortable(),
                Tables\Columns\TextColumn::make('current_validity_state')
                    ->label('Validity')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingRiskGovernanceValidity))
                    ->color(BadgeRenderer::color(BadgeDomain::FindingRiskGovernanceValidity))
                    ->icon(BadgeRenderer::icon(BadgeDomain::FindingRiskGovernanceValidity))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingRiskGovernanceValidity))
                    ->sortable(),
                Tables\Columns\TextColumn::make('finding.severity')
                    ->label('Severity')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::FindingSeverity))
                    ->color(BadgeRenderer::color(BadgeDomain::FindingSeverity))
                    ->icon(BadgeRenderer::icon(BadgeDomain::FindingSeverity))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingSeverity))
                    ->placeholder('—')
                    ->sortable(),
                Tables\Columns\TextColumn::make('finding_summary')
                    ->label('Finding')
                    ->state(fn (FindingException $record): string => static::findingSummary($record))
                    ->searchable()
                    ->wrap()
                    ->limit(60),
                Tables\Columns\TextColumn::make('governance_warning')
                    ->label('Governance warning')
                    ->state(fn (FindingException $record): ?string => static::governanceWarning($record))
                    ->color(fn (FindingException $record): string => static::governanceWarningColor($record))
                    ->wrap(),
                Tables\Columns\TextColumn::make('requester.name')
                    ->label('Requested by')
                    ->placeholder('—'),
                Tables\Columns\TextColumn::make('owner.name')
                    ->label('Owner')
                    ->placeholder('—'),
                Tables\Columns\TextColumn::make('review_due_at')
                    ->label('Review due')
                    ->dateTime()
                    ->placeholder('—')
                    ->sortable()
                    ->description(fn (FindingException $record): ?string => static::relativeTimeDescription($record->review_due_at)),
                Tables\Columns\TextColumn::make('expires_at')
                    ->label('Expires')
                    ->dateTime()
                    ->placeholder('—')
                    ->sortable()
                    ->description(fn (FindingException $record): ?string => static::relativeTimeDescription($record->expires_at)),
                Tables\Columns\TextColumn::make('requested_at')
                    ->label('Requested')
                    ->dateTime()
                    ->sortable(),
            ])
            ->filters([
                SelectFilter::make('status')
                    ->options(FilterOptionCatalog::findingExceptionStatuses()),
                SelectFilter::make('current_validity_state')
                    ->label('Validity')
                    ->options(FilterOptionCatalog::findingExceptionValidityStates()),
                SelectFilter::make('finding_severity')
                    ->label('Finding severity')
                    ->options(FilterOptionCatalog::findingSeverities())
                    ->query(fn (Builder $query, array $data): Builder => filled($data['value'])
                        ? $query->whereHas('finding', fn (Builder $q) => $q->where('severity', $data['value']))
                        : $query),
            ])
            ->actions([
                Action::make('renew_exception')
                    ->label('Renew exception')
                    ->icon('heroicon-o-arrow-path')
                    ->color('warning')
                    ->visible(fn (FindingException $record): bool => static::canManageRecord($record) && $record->canBeRenewed())
                    ->requiresConfirmation()
                    ->form([
                        Select::make('owner_user_id')
                            ->label('Owner')
                            ->required()
                            ->options(fn (): array => static::tenantMemberOptions())
                            ->searchable(),
                        Textarea::make('request_reason')
                            ->label('Renewal reason')
                            ->rows(4)
                            ->required()
                            ->maxLength(2000),
                        DateTimePicker::make('review_due_at')
                            ->label('Review due at')
                            ->required()
                            ->seconds(false),
                        DateTimePicker::make('expires_at')
                            ->label('Requested expiry')
                            ->seconds(false),
                        Repeater::make('evidence_references')
                            ->label('Evidence references')
                            ->schema([
                                TextInput::make('label')
                                    ->label('Label')
                                    ->required()
                                    ->maxLength(255),
                                TextInput::make('source_type')
                                    ->label('Source type')
                                    ->required()
                                    ->maxLength(255),
                                TextInput::make('source_id')
                                    ->label('Source ID')
                                    ->maxLength(255),
                                TextInput::make('source_fingerprint')
                                    ->label('Fingerprint')
                                    ->maxLength(255),
                                DateTimePicker::make('measured_at')
                                    ->label('Measured at')
                                    ->seconds(false),
                            ])
                            ->defaultItems(0)
                            ->collapsed(),
                    ])
                    ->action(function (FindingException $record, array $data, FindingExceptionService $service): void {
                        $user = auth()->user();

                        if (! $user instanceof User || ! $record->tenant instanceof ManagedEnvironment) {
                            abort(404);
                        }

                        try {
                            $service->renew($record, $user, $data);
                        } catch (InvalidArgumentException $exception) {
                            Notification::make()
                                ->title('Renewal request failed')
                                ->body($exception->getMessage())
                                ->danger()
                                ->send();

                            return;
                        }

                        Notification::make()
                            ->title('Renewal request submitted')
                            ->success()
                            ->send();
                    }),
                Action::make('revoke_exception')
                    ->label('Revoke exception')
                    ->icon('heroicon-o-no-symbol')
                    ->color('danger')
                    ->visible(fn (FindingException $record): bool => static::canManageRecord($record) && $record->canBeRevoked())
                    ->requiresConfirmation()
                    ->form([
                        Textarea::make('revocation_reason')
                            ->label('Revocation reason')
                            ->rows(4)
                            ->required()
                            ->maxLength(2000),
                    ])
                    ->action(function (FindingException $record, array $data, FindingExceptionService $service): void {
                        $user = auth()->user();

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

                        try {
                            $service->revoke($record, $user, $data);
                        } catch (InvalidArgumentException $exception) {
                            Notification::make()
                                ->title('Exception revocation failed')
                                ->body($exception->getMessage())
                                ->danger()
                                ->send();

                            return;
                        }

                        Notification::make()
                            ->title('Exception revoked')
                            ->success()
                            ->send();
                    }),
            ])
            ->bulkActions([])
            ->emptyStateHeading('No exceptions match this view')
            ->emptyStateDescription('Exception requests are created from finding detail when a governed risk acceptance review is needed.')
            ->emptyStateIcon('heroicon-o-shield-exclamation')
            ->emptyStateActions([
                Action::make('open_findings')
                    ->label('Open findings')
                    ->icon('heroicon-o-arrow-top-right-on-square')
                    ->color('gray')
                    ->url(fn (): string => FindingResource::getUrl('index')),
            ]);
    }

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

    /**
     * @return array<int, string|array<int|string, mixed>>
     */
    private static function relationshipsForView(): array
    {
        return [
            'tenant',
            'requester',
            'owner',
            'approver',
            'currentDecision',
            'decisions.actor',
            'evidenceReferences',
            'finding' => fn ($query) => $query->withSubjectDisplayName(),
        ];
    }

    /**
     * @return array<int, string>
     */
    private static function tenantMemberOptions(): array
    {
        $tenant = static::resolveTenantContextForCurrentPanel();

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

        /** @var ManagedEnvironmentAccessScopeResolver $scopeResolver */
        $scopeResolver = app(ManagedEnvironmentAccessScopeResolver::class);

        return User::query()
            ->whereHas('workspaceMemberships', fn (Builder $query): Builder => $query->where('workspace_id', (int) $tenant->workspace_id))
            ->orderBy('name')
            ->orderBy('email')
            ->get(['id', 'name', 'email'])
            ->filter(fn (User $user): bool => $scopeResolver->canAccess($user, $tenant))
            ->mapWithKeys(fn (User $user): array => [
                (int) $user->id => trim((string) ($user->name ?: $user->email)),
            ])
            ->all();
    }

    private static function findingSummary(FindingException $record): string
    {
        $finding = $record->finding;

        if (! $finding instanceof \App\Models\Finding) {
            return 'Finding #'.$record->finding_id;
        }

        $displayName = $finding->resolvedSubjectDisplayName();
        $findingType = $finding->finding_type;

        $parts = [];

        if (is_string($displayName) && trim($displayName) !== '') {
            $parts[] = trim($displayName);
        }

        if (is_string($findingType) && trim($findingType) !== '') {
            $label = str_replace('_', ' ', trim($findingType));
            $parts[] = '('.ucfirst($label).')';
        }

        if ($parts !== []) {
            return implode(' ', $parts);
        }

        return 'Finding #'.$record->finding_id;
    }

    public static function relativeTimeDescription(mixed $date): ?string
    {
        if (! $date instanceof \DateTimeInterface) {
            return null;
        }

        $carbon = \Illuminate\Support\Carbon::instance($date);

        if ($carbon->isToday()) {
            return 'Today';
        }

        if ($carbon->isPast()) {
            return $carbon->diffForHumans();
        }

        if ($carbon->isTomorrow()) {
            return 'Tomorrow';
        }

        $daysUntil = (int) now()->startOfDay()->diffInDays($carbon->startOfDay());

        if ($daysUntil <= 14) {
            return 'In '.$daysUntil.' days';
        }

        return $carbon->diffForHumans();
    }

    private static function canManageRecord(FindingException $record): bool
    {
        $user = auth()->user();

        return $user instanceof User
            && $record->tenant instanceof ManagedEnvironment
            && $user->canAccessTenant($record->tenant)
            && $user->can(Capabilities::FINDING_EXCEPTION_MANAGE, $record->tenant);
    }

    private static function governanceWarning(FindingException $record): ?string
    {
        $finding = $record->relationLoaded('finding')
            ? $record->finding
            : $record->finding()->withSubjectDisplayName()->first();

        if (! $finding instanceof \App\Models\Finding) {
            return null;
        }

        return app(FindingRiskGovernanceResolver::class)->resolveWarningMessage($finding, $record);
    }

    private static function governanceWarningColor(FindingException $record): string
    {
        $finding = $record->relationLoaded('finding')
            ? $record->finding
            : $record->finding()->withSubjectDisplayName()->first();

        if ((string) $record->current_validity_state === FindingException::VALIDITY_EXPIRING) {
            return 'warning';
        }

        if ($finding instanceof \App\Models\Finding && $record->requiresFreshDecisionForFinding($finding)) {
            return 'warning';
        }

        return 'danger';
    }

    public static function canAccessApprovalQueueForTenant(?ManagedEnvironment $tenant = null): bool
    {
        $tenant ??= static::resolveTenantContextForCurrentPanel();
        $user = auth()->user();

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

        $workspace = Workspace::query()->whereKey($tenant->workspace_id)->first();

        if (! $workspace instanceof Workspace) {
            return false;
        }

        /** @var WorkspaceCapabilityResolver $resolver */
        $resolver = app(WorkspaceCapabilityResolver::class);

        return $resolver->isMember($user, $workspace)
            && $resolver->can($user, $workspace, Capabilities::FINDING_EXCEPTION_APPROVE);
    }

    public static function approvalQueueUrl(?ManagedEnvironment $tenant = null): ?string
    {
        $tenant ??= static::resolveTenantContextForCurrentPanel();

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

        return route('admin.finding-exceptions.open-queue', [
            'environment' => (string) $tenant->external_id,
        ]);
    }
}
