<?php

declare(strict_types=1);

namespace App\Filament\Pages\Monitoring;

use App\Models\AuditLog as AuditLogModel;
use App\Models\ManagedEnvironment;
use App\Models\SupportAccessGrant;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Filament\CanonicalAdminEnvironmentFilterState;
use App\Support\Filament\FilterOptionCatalog;
use App\Support\Filament\FilterPresets;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\Navigation\RelatedNavigationResolver;
use App\Support\OperateHub\OperateHubShell;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\ActionSurfaceDefaults;
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\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 Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use UnitEnum;

class AuditLog extends Page implements HasTable
{
    use InteractsWithTable;

    protected const MONITORING_PAGE_STATE_CONTRACT = [
        'surfaceKey' => 'audit_log',
        'surfaceType' => 'selected_record_monitoring',
        'stateFields' => [
            [
                'stateKey' => 'event',
                'stateClass' => 'inspect',
                'carrier' => 'query_param',
                'queryRole' => 'durable_restorable',
                'shareable' => true,
                'restorableOnRefresh' => true,
                'tenantSensitive' => false,
                'invalidFallback' => 'clear_selection_and_continue',
            ],
            [
                'stateKey' => 'supportAccess',
                'stateClass' => 'contextual_prefilter',
                'carrier' => 'query_param',
                'queryRole' => 'durable_restorable',
                'shareable' => true,
                'restorableOnRefresh' => true,
                'tenantSensitive' => false,
                'invalidFallback' => 'discard_and_continue',
            ],
            [
                'stateKey' => 'managed_environment_id',
                'stateClass' => 'contextual_prefilter',
                'carrier' => 'session',
                'queryRole' => 'durable_restorable',
                'shareable' => false,
                'restorableOnRefresh' => true,
                'tenantSensitive' => true,
                'invalidFallback' => 'discard_and_continue',
            ],
            [
                'stateKey' => 'tableSearch',
                'stateClass' => 'shareable_restorable',
                'carrier' => 'session',
                'queryRole' => 'unsupported',
                'shareable' => false,
                'restorableOnRefresh' => true,
                'tenantSensitive' => false,
                'invalidFallback' => 'discard_and_continue',
            ],
        ],
        'hydrationRule' => [
            'precedenceOrder' => ['query', 'session', 'default'],
            'appliesOnInitialMountOnly' => true,
            'activeStateBecomesAuthoritativeAfterMount' => true,
            'clearsOnTenantSwitch' => ['managed_environment_id', 'action', 'actor_label', 'resource_type'],
            'invalidRequestedStateFallback' => 'clear_selection_and_continue',
        ],
        'inspectContract' => [
            'primaryModel' => AuditLogModel::class,
            'selectedStateKey' => 'selectedAuditLogId',
            'openedBy' => ['query_param', 'inspect_action'],
            'presentation' => 'inline_detail',
            'shareable' => true,
            'invalidSelectionFallback' => 'clear_selection_and_continue',
        ],
        'shareableStateKeys' => ['event', 'supportAccess'],
        'localOnlyStateKeys' => [],
    ];

    public ?int $selectedAuditLogId = null;

    public bool $supportAccessOnly = false;

    protected static bool $isDiscovered = false;

    protected static bool $shouldRegisterNavigation = false;

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

    protected static ?string $navigationLabel = 'Audit Log';

    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-clipboard-document-list';

    protected static ?string $slug = 'audit-log';

    protected static ?string $title = 'Audit Log';

    protected string $view = 'filament.pages.monitoring.audit-log';

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

    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::RunLog, ActionSurfaceType::HistoryAudit)
            ->withDefaults(new ActionSurfaceDefaults(
                moreGroupLabel: 'More',
                exportIsDefaultBulkActionForReadOnly: false,
            ))
            ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the Monitoring scope visible and expose selected-event detail actions.')
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ViewAction->value)
            ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'Audit history is immutable and intentionally omits bulk actions.')
            ->satisfy(ActionSurfaceSlot::ListEmptyState, 'The table exposes a clear-filters CTA when no audit events match the current view.')
            ->satisfy(ActionSurfaceSlot::DetailHeader, 'Selected-event detail keeps close-inspection and related-navigation actions at the page header.');
    }

    /**
     * @return array<string, mixed>
     */
    public static function monitoringPageStateContract(): array
    {
        return self::MONITORING_PAGE_STATE_CONTRACT;
    }

    public function mount(): void
    {
        $this->authorizePageAccess();
        $this->supportAccessOnly = request()->boolean('supportAccess');
        $requestedEventId = is_numeric(request()->query('event')) ? (int) request()->query('event') : null;

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

        $this->mountInteractsWithTable();

        if ($requestedEventId !== null) {
            $this->selectedAuditLogId = $this->resolveSelectedAuditLogId($requestedEventId);
        }
    }

    /**
     * @return array<Action>
     */
    protected function getHeaderActions(): array
    {
        $actions = app(OperateHubShell::class)->headerActions(
            scopeActionName: 'operate_hub_scope_audit_log',
            returnActionName: 'operate_hub_return_audit_log',
        );

        $navigationContext = CanonicalNavigationContext::fromRequest(request());

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

        array_splice($actions, 1, 0, [
            Action::make('support_access_history_filter')
                ->label($this->supportAccessOnly ? 'Show all audit events' : 'Support access history')
                ->icon($this->supportAccessOnly ? 'heroicon-o-list-bullet' : 'heroicon-o-lifebuoy')
                ->color('gray')
                ->url($this->auditLogUrl([
                    'supportAccess' => $this->supportAccessOnly ? null : true,
                    'event' => null,
                ])),
            Action::make('export_support_access_history')
                ->label('Export support access history')
                ->icon('heroicon-o-arrow-down-tray')
                ->color('gray')
                ->action(fn (): StreamedResponse => $this->exportSupportAccessHistory()),
        ]);

        $selectedAudit = $this->selectedAuditRecord();
        $selectedAuditLink = $selectedAudit instanceof AuditLogModel
            ? $this->auditTargetLink($selectedAudit)
            : null;

        if ($selectedAudit instanceof AuditLogModel) {
            array_splice($actions, 1, 0, array_values(array_filter([
                Action::make('close_selected_audit_event')
                    ->label('Close details')
                    ->icon('heroicon-o-x-mark')
                    ->color('gray')
                    ->url($this->auditLogUrl(['event' => null])),
                $selectedAuditLink !== null
                    ? Action::make('open_selected_audit_target')
                        ->label($selectedAuditLink['label'])
                        ->icon('heroicon-o-arrow-top-right-on-square')
                        ->color('gray')
                        ->url($selectedAuditLink['url'])
                    : null,
            ])));
        }

        return $actions;
    }

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

    public function updatedTableSearch(): void
    {
        $this->normalizeSelectedAuditLogId();
    }

    public function table(Table $table): Table
    {
        return $table
            ->query(fn (): Builder => $this->auditBaseQuery())
            ->defaultSort('recorded_at', 'desc')
            ->paginated(TablePaginationProfiles::customPage())
            ->persistFiltersInSession()
            ->persistSearchInSession()
            ->persistSortInSession()
            ->columns([
                TextColumn::make('outcome')
                    ->label('Outcome')
                    ->badge()
                    ->getStateUsing(fn (AuditLogModel $record): string => $record->normalizedOutcome()->value)
                    ->formatStateUsing(fn (string $state): string => BadgeRenderer::label(BadgeDomain::AuditOutcome)($state))
                    ->color(fn (string $state): string => BadgeRenderer::color(BadgeDomain::AuditOutcome)($state))
                    ->icon(fn (string $state): ?string => BadgeRenderer::icon(BadgeDomain::AuditOutcome)($state))
                    ->iconColor(fn (string $state): ?string => BadgeRenderer::iconColor(BadgeDomain::AuditOutcome)($state)),
                TextColumn::make('summary')
                    ->label('Event')
                    ->getStateUsing(fn (AuditLogModel $record): string => $record->summaryText())
                    ->description(fn (AuditLogModel $record): string => AuditActionId::labelFor((string) $record->action))
                    ->searchable()
                    ->wrap(),
                TextColumn::make('actor_label')
                    ->label('Actor')
                    ->getStateUsing(fn (AuditLogModel $record): string => $record->actorDisplayLabel())
                    ->description(fn (AuditLogModel $record): string => BadgeRenderer::label(BadgeDomain::AuditActorType)($record->actorSnapshot()->type->value))
                    ->searchable(),
                TextColumn::make('target_label')
                    ->label('Target')
                    ->getStateUsing(fn (AuditLogModel $record): string => $record->targetDisplayLabel() ?? 'No target snapshot')
                    ->searchable()
                    ->toggleable(),
                TextColumn::make('tenant.name')
                    ->label('ManagedEnvironment')
                    ->formatStateUsing(fn (?string $state): string => $state ?: 'Workspace')
                    ->toggleable(),
                TextColumn::make('recorded_at')
                    ->label('Recorded')
                    ->since()
                    ->sortable(),
            ])
            ->filters([
                SelectFilter::make('managed_environment_id')
                    ->label('ManagedEnvironment')
                    ->options(fn (): array => $this->tenantFilterOptions())
                    ->default(fn (): ?string => $this->defaultTenantFilter())
                    ->searchable(),
                SelectFilter::make('action')
                    ->label('Event type')
                    ->options(fn (): array => $this->actionFilterOptions())
                    ->searchable(),
                SelectFilter::make('outcome')
                    ->label('Outcome')
                    ->options(FilterOptionCatalog::auditOutcomes()),
                SelectFilter::make('actor_label')
                    ->label('Actor')
                    ->options(fn (): array => $this->actorFilterOptions())
                    ->searchable(),
                SelectFilter::make('resource_type')
                    ->label('Target type')
                    ->options(fn (): array => $this->targetTypeFilterOptions()),
                FilterPresets::dateRange('recorded_at', 'Recorded', 'recorded_at'),
            ])
            ->actions([
                Action::make('inspect')
                    ->label('Inspect event')
                    ->icon('heroicon-o-eye')
                    ->color('gray')
                    ->url(fn (AuditLogModel $record): string => $this->auditLogUrl(['event' => (int) $record->getKey()])),
            ])
            ->bulkActions([])
            ->emptyStateHeading('No audit events match this view')
            ->emptyStateDescription('Clear the current search or filters to return to the workspace-wide audit history.')
            ->emptyStateIcon('heroicon-o-funnel')
            ->emptyStateActions([
                Action::make('clear_filters')
                    ->label('Clear filters')
                    ->icon('heroicon-o-x-mark')
                    ->color('gray')
                    ->action(function (): void {
                        $this->selectedAuditLogId = null;
                        $this->resetTable();
                    }),
            ]);
    }

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

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

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

        $tenants = collect($user->getTenants(Filament::getCurrentOrDefaultPanel()))
            ->filter(static fn (ManagedEnvironment $tenant): bool => (int) $tenant->workspace_id === (int) $workspaceId)
            ->filter(static fn (ManagedEnvironment $tenant): bool => $tenant->isActive())
            ->keyBy(fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey())
            ->all();

        return $this->authorizedTenants = $tenants;
    }

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

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

        $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());
        $workspace = is_numeric($workspaceId) ? Workspace::query()->whereKey((int) $workspaceId)->first() : null;

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

        $resolver = app(WorkspaceCapabilityResolver::class);

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

        if (! $resolver->can($user, $workspace, Capabilities::AUDIT_VIEW)) {
            abort(403);
        }
    }

    private function auditBaseQuery(): Builder
    {
        $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());
        $authorizedTenantIds = array_map(
            static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey(),
            $this->authorizedTenants(),
        );

        return AuditLogModel::query()
            ->with(['tenant', 'workspace', 'operationRun'])
            ->forWorkspace((int) $workspaceId)
            ->where(function (Builder $query) use ($authorizedTenantIds): void {
                $query->whereNull('managed_environment_id');

                if ($authorizedTenantIds !== []) {
                    $query->orWhereIn('managed_environment_id', $authorizedTenantIds);
                }
            })
            ->when($this->supportAccessOnly, function (Builder $query): void {
                $query->whereIn('action', SupportAccessGrant::supportAccessAuditActions());
            })
            ->latestFirst();
    }

    private function resolveAuditLog(int $auditLogId): AuditLogModel
    {
        $record = $this->auditBaseQuery()
            ->whereKey($auditLogId)
            ->first();

        if (! $record instanceof AuditLogModel) {
            throw new NotFoundHttpException;
        }

        return $record;
    }

    public function selectedAuditRecord(): ?AuditLogModel
    {
        if (! is_int($this->selectedAuditLogId) || $this->selectedAuditLogId <= 0) {
            return null;
        }

        $this->normalizeSelectedAuditLogId();

        if (! is_int($this->selectedAuditLogId) || $this->selectedAuditLogId <= 0) {
            return null;
        }

        try {
            return $this->resolveAuditLog($this->selectedAuditLogId);
        } catch (NotFoundHttpException) {
            return null;
        }
    }

    /**
     * @return array{label: string, url: string}|null
     */
    public function selectedAuditTargetLink(): ?array
    {
        $record = $this->selectedAuditRecord();

        if (! $record instanceof AuditLogModel) {
            return null;
        }

        return $this->auditTargetLink($record);
    }

    /**
     * @return array{label: string, url: string}|null
     */
    private function auditTargetLink(AuditLogModel $record): ?array
    {
        return app(RelatedNavigationResolver::class)->auditTargetLink($record);
    }

    private function auditLogUrl(array $overrides = []): string
    {
        $parameters = array_merge(
            $this->navigationContext()?->toQuery() ?? [],
            ['supportAccess' => $this->supportAccessOnly ? true : null],
            ['event' => $this->selectedAuditLogId],
            $overrides,
        );

        return route(
            'admin.monitoring.audit-log',
            array_filter($parameters, static fn (mixed $value): bool => $value !== null && $value !== '' && $value !== []),
        );
    }

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

    private function normalizeSelectedAuditLogId(): void
    {
        if (! is_int($this->selectedAuditLogId) || $this->selectedAuditLogId <= 0) {
            $this->selectedAuditLogId = null;

            return;
        }

        $this->selectedAuditLogId = $this->resolveSelectedAuditLogId($this->selectedAuditLogId);
    }

    private function resolveSelectedAuditLogId(int $auditLogId): ?int
    {
        try {
            $record = $this->resolveAuditLog($auditLogId);
        } catch (NotFoundHttpException) {
            return null;
        }

        return $this->selectedAuditVisible((int) $record->getKey())
            ? (int) $record->getKey()
            : null;
    }

    private function selectedAuditVisible(int $auditLogId): bool
    {
        $record = $this->resolveAuditLog($auditLogId);

        return $this->matchesSelectedAuditFilters($record)
            && $this->matchesSelectedAuditSearch($record);
    }

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

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

    private function currentTableSearchState(): string
    {
        $search = trim((string) ($this->tableSearch ?? ''));

        if ($search !== '') {
            return $search;
        }

        $persisted = session()->get($this->getTableSearchSessionKey(), '');

        return trim(is_string($persisted) ? $persisted : '');
    }

    private function matchesSelectedAuditFilters(AuditLogModel $record): bool
    {
        if (
            $this->supportAccessOnly
            && ! in_array((string) $record->action, SupportAccessGrant::supportAccessAuditActions(), true)
        ) {
            return false;
        }

        $filters = $this->currentTableFiltersState();

        $tenantFilter = data_get($filters, 'managed_environment_id.value');

        if (is_numeric($tenantFilter) && (int) $record->managed_environment_id !== (int) $tenantFilter) {
            return false;
        }

        $actionFilter = data_get($filters, 'action.value');

        if (is_string($actionFilter) && $actionFilter !== '' && (string) $record->action !== $actionFilter) {
            return false;
        }

        $outcomeFilter = data_get($filters, 'outcome.value');

        if (is_string($outcomeFilter) && $outcomeFilter !== '' && $record->normalizedOutcome()->value !== $outcomeFilter) {
            return false;
        }

        $actorFilter = data_get($filters, 'actor_label.value');

        if (is_string($actorFilter) && $actorFilter !== '' && (string) $record->actor_label !== $actorFilter) {
            return false;
        }

        $resourceTypeFilter = data_get($filters, 'resource_type.value');

        if (is_string($resourceTypeFilter) && $resourceTypeFilter !== '' && (string) $record->resource_type !== $resourceTypeFilter) {
            return false;
        }

        return true;
    }

    private function matchesSelectedAuditSearch(AuditLogModel $record): bool
    {
        $search = Str::lower($this->currentTableSearchState());

        if ($search === '') {
            return true;
        }

        $haystack = Str::lower(implode(' ', [
            $record->summaryText(),
            $record->actorDisplayLabel(),
            $record->targetDisplayLabel() ?? '',
        ]));

        return str_contains($haystack, $search);
    }

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

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

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

        return array_key_exists((int) $activeEnvironment->getKey(), $this->authorizedTenants())
            ? (string) $activeEnvironment->getKey()
            : null;
    }

    /**
     * @return array<string, string>
     */
    private function actionFilterOptions(): array
    {
        $values = (clone $this->auditBaseQuery())
            ->reorder()
            ->select('action')
            ->distinct()
            ->orderBy('action')
            ->pluck('action')
            ->all();

        return FilterOptionCatalog::auditActions($values);
    }

    /**
     * @return array<string, string>
     */
    private function actorFilterOptions(): array
    {
        return (clone $this->auditBaseQuery())
            ->reorder()
            ->whereNotNull('actor_label')
            ->select('actor_label')
            ->distinct()
            ->orderBy('actor_label')
            ->pluck('actor_label', 'actor_label')
            ->all();
    }

    /**
     * @return array<string, string>
     */
    private function targetTypeFilterOptions(): array
    {
        $values = (clone $this->auditBaseQuery())
            ->reorder()
            ->whereNotNull('resource_type')
            ->select('resource_type')
            ->distinct()
            ->orderBy('resource_type')
            ->pluck('resource_type')
            ->all();

        return FilterOptionCatalog::auditTargetTypes($values);
    }

    public function exportSupportAccessHistory(): StreamedResponse
    {
        $filename = 'support-access-history-'.now()->format('Ymd-His').'.csv';

        return response()->streamDownload(function (): void {
            $handle = fopen('php://output', 'w');

            if ($handle === false) {
                return;
            }

            fputcsv($handle, [
                'recorded_at',
                'action',
                'outcome',
                'actor',
                'target',
                'scope',
                'reason',
                'grant_id',
            ]);

            $this->supportAccessAuditQuery()
                ->reorder()
                ->orderBy('recorded_at')
                ->orderBy('id')
                ->cursor()
                ->each(function (AuditLogModel $record) use ($handle): void {
                    $metadata = is_array($record->metadata) ? $record->metadata : [];

                    fputcsv($handle, [
                        $this->csvCell((string) $record->recorded_at?->toISOString()),
                        $this->csvCell((string) $record->action),
                        $this->csvCell($record->normalizedOutcome()->value),
                        $this->csvCell($record->actorDisplayLabel()),
                        $this->csvCell($record->targetDisplayLabel() ?? ''),
                        $this->csvCell((string) ($metadata['scope'] ?? '')),
                        $this->csvCell((string) ($metadata['reason'] ?? '')),
                        $this->csvCell((string) ($metadata['support_access_grant_id'] ?? '')),
                    ]);
                });

            fclose($handle);
        }, $filename, [
            'Content-Type' => 'text/csv; charset=UTF-8',
        ]);
    }

    private function supportAccessAuditQuery(): Builder
    {
        return $this->auditBaseQuery()
            ->whereIn('action', SupportAccessGrant::supportAccessAuditActions());
    }

    private function csvCell(string $value): string
    {
        $trimmed = trim($value);

        if ($trimmed !== '' && in_array($trimmed[0], ['=', '+', '-', '@'], true)) {
            return "'".$trimmed;
        }

        return $value;
    }
}
