<?php

declare(strict_types=1);

namespace App\Filament\Resources\FindingExceptionResource\Pages;

use App\Filament\Resources\FindingExceptionResource;
use App\Models\FindingException;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Auth\ManagedEnvironmentAccessScopeResolver;
use App\Services\Findings\FindingExceptionService;
use App\Support\Auth\Capabilities;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\Ui\GovernanceActions\GovernanceActionCatalog;
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\Notifications\Notification;
use Filament\Resources\Pages\ViewRecord;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;

class ViewFindingException extends ViewRecord
{
    protected static string $resource = FindingExceptionResource::class;

    protected function resolveRecord(int|string $key): Model
    {
        return FindingExceptionResource::resolveScopedRecordOrFail($key);
    }

    protected function getHeaderActions(): array
    {
        $renewRule = GovernanceActionCatalog::rule('renew_exception');
        $revokeRule = GovernanceActionCatalog::rule('revoke_exception');

        $actions = [];
        $navigationContext = $this->navigationContext();

        if ($navigationContext?->backLinkLabel !== null && $navigationContext->backLinkUrl !== null) {
            $actions[] = Action::make('return_to_decision_register')
                ->label($navigationContext->backLinkLabel)
                ->icon('heroicon-o-arrow-left')
                ->color('gray')
                ->url($navigationContext->backLinkUrl);
        }

        return array_merge($actions, [
            Action::make('renew_exception')
                ->label($renewRule->canonicalLabel)
                ->icon('heroicon-o-arrow-path')
                ->color('primary')
                ->visible(fn (): bool => $this->canManageRecord() && $this->getRecord() instanceof FindingException && $this->getRecord()->canBeRenewed())
                ->fillForm(fn (): array => [
                    'owner_user_id' => $this->getRecord() instanceof FindingException ? $this->getRecord()->owner_user_id : null,
                ])
                ->requiresConfirmation()
                ->modalHeading($renewRule->modalHeading)
                ->modalDescription($renewRule->modalDescription)
                ->form([
                    Select::make('owner_user_id')
                        ->label('Owner')
                        ->required()
                        ->options(fn (): array => FindingExceptionResource::canViewAny() ? $this->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 (array $data, FindingExceptionService $service) use ($renewRule): void {
                    $record = $this->getRecord();
                    $user = auth()->user();

                    if (! $record instanceof FindingException || ! $user instanceof User) {
                        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($renewRule->successTitle)
                        ->success()
                        ->send();

                    $this->refreshFormData(['status', 'current_validity_state', 'review_due_at']);
                }),
            Action::make('revoke_exception')
                ->label($revokeRule->canonicalLabel)
                ->icon('heroicon-o-no-symbol')
                ->color('danger')
                ->visible(fn (): bool => $this->canManageRecord() && $this->getRecord() instanceof FindingException && $this->getRecord()->canBeRevoked())
                ->requiresConfirmation()
                ->modalHeading($revokeRule->modalHeading)
                ->modalDescription($revokeRule->modalDescription)
                ->form([
                    Textarea::make('revocation_reason')
                        ->label('Revocation reason')
                        ->rows(4)
                        ->required()
                        ->maxLength(2000),
                ])
                ->action(function (array $data, FindingExceptionService $service) use ($revokeRule): void {
                    $record = $this->getRecord();
                    $user = auth()->user();

                    if (! $record instanceof FindingException || ! $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($revokeRule->successTitle)
                        ->success()
                        ->send();

                    $this->refreshFormData(['status', 'current_validity_state', 'revocation_reason', 'revoked_at']);
                }),
        ]);
    }

    public function getSubheading(): ?string
    {
        $navigationContext = $this->navigationContext();

        if ($navigationContext?->sourceSurface === 'governance.decision_register') {
            return 'Opened from the workspace decision register. Use the back action to return to the same register scope.';
        }

        return null;
    }

    /**
     * @return array<int, string>
     */
    private function tenantMemberOptions(): array
    {
        $record = $this->getRecord();

        if (! $record instanceof FindingException) {
            return [];
        }

        $tenant = $record->tenant;

        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 function canManageRecord(): bool
    {
        $record = $this->getRecord();
        $user = auth()->user();

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

    private function navigationContext(): ?CanonicalNavigationContext
    {
        return CanonicalNavigationContext::fromRequest(request());
    }
}
