<?php

declare(strict_types=1);

namespace App\Filament\Pages\Monitoring;

use App\Filament\Concerns\ClearsWorkspaceHubEnvironmentFilterState;
use App\Filament\Resources\EvidenceSnapshotResource;
use App\Models\EnvironmentReview;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\Workspace;
use App\Support\EnvironmentReviewStatus;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
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\Ui\GovernanceArtifactTruth\ArtifactTruthEnvelope;
use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthPresenter;
use App\Support\Ui\GovernanceArtifactTruth\CompressedGovernanceOutcome;
use App\Support\Ui\GovernanceArtifactTruth\SurfaceCompressionContext;
use App\Support\Workspaces\WorkspaceContext;
use BackedEnum;
use Filament\Actions\Action;
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\Auth\AuthenticationException;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use UnitEnum;

class EvidenceOverview extends Page implements HasTable
{
    use ClearsWorkspaceHubEnvironmentFilterState;
    use InteractsWithTable;

    protected const MONITORING_PAGE_STATE_CONTRACT = [
        'surfaceKey' => 'evidence_overview',
        'surfaceType' => 'simple_monitoring',
        'stateFields' => [
            [
                'stateKey' => 'environment_id',
                'stateClass' => 'contextual_prefilter',
                'carrier' => 'query_param',
                'queryRole' => 'durable_restorable',
                'shareable' => true,
                'restorableOnRefresh' => true,
                'tenantSensitive' => true,
                'invalidFallback' => 'discard_and_continue',
            ],
            [
                'stateKey' => 'search',
                'stateClass' => 'contextual_prefilter',
                'carrier' => 'query_param',
                'queryRole' => 'durable_restorable',
                'shareable' => true,
                'restorableOnRefresh' => true,
                'tenantSensitive' => false,
                'invalidFallback' => 'discard_and_continue',
            ],
            [
                'stateKey' => 'tableFilters',
                'stateClass' => 'shareable_restorable',
                'carrier' => 'session',
                'queryRole' => 'unsupported',
                'shareable' => false,
                'restorableOnRefresh' => true,
                'tenantSensitive' => true,
                'invalidFallback' => 'discard_and_continue',
            ],
            [
                'stateKey' => 'tableSort',
                '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' => ['environment_id', 'managed_environment_id'],
            'invalidRequestedStateFallback' => 'discard_and_continue',
        ],
        'inspectContract' => [
            'primaryModel' => 'none',
            'selectedStateKey' => null,
            'openedBy' => ['row_navigation'],
            'presentation' => 'navigate_to_canonical_detail',
            'shareable' => false,
            'invalidSelectionFallback' => 'discard_and_continue',
        ],
        'shareableStateKeys' => ['environment_id', 'search'],
        'localOnlyStateKeys' => [],
    ];

    protected static bool $isDiscovered = false;

    protected static bool $shouldRegisterNavigation = false;

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

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

    protected static ?string $title = 'Evidence Overview';

    protected string $view = 'filament.pages.monitoring.evidence-overview';

    /**
     * @var list<array<string, mixed>>
     */
    public array $rows = [];

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

    private ?Collection $cachedSnapshots = null;

    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport)
            ->satisfy(ActionSurfaceSlot::ListHeader, 'The overview header exposes a clear-filters action when a tenant prefilter is active.')
            ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
            ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The overview exposes a single drill-down link per row without a More menu.')
            ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The overview does not expose bulk actions.')
            ->satisfy(ActionSurfaceSlot::ListEmptyState, 'The empty state explains the current scope and offers a clear-filters CTA.');
    }

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

    public function mount(): void
    {
        $this->authorizeWorkspaceAccess();
        $this->resetWorkspaceHubEnvironmentFilterStateForCleanEntry(request());
        $this->seedTableStateFromQuery();

        $this->mountInteractsWithTable();
        $this->resetWorkspaceHubEnvironmentFilterStateForCleanEntry(request());
        $this->rows = $this->rowsForState($this->tableFilters ?? [], $this->tableSearch)->values()->all();
    }

    public function table(Table $table): Table
    {
        return $table
            ->defaultSort('tenant_name')
            ->defaultPaginationPageOption(25)
            ->paginated(TablePaginationProfiles::customPage())
            ->persistFiltersInSession()
            ->persistSearchInSession()
            ->persistSortInSession()
            ->searchable()
            ->searchPlaceholder('Search tenant or next step')
            ->records(function (
                ?string $sortColumn,
                ?string $sortDirection,
                ?string $search,
                array $filters,
                int $page,
                int $recordsPerPage
            ): LengthAwarePaginator {
                $rows = $this->rowsForState($filters, $search);
                $rows = $this->sortRows($rows, $sortColumn, $sortDirection);

                return $this->paginateRows($rows, $page, $recordsPerPage);
            })
            ->filters([
                SelectFilter::make('managed_environment_id')
                    ->label('Environment')
                    ->options(fn (): array => $this->tenantFilterOptions())
                    ->searchable(),
            ])
            ->columns([
                TextColumn::make('tenant_name')
                    ->label('Environment')
                    ->sortable(),
                TextColumn::make('artifact_truth_label')
                    ->label('Outcome')
                    ->badge()
                    ->color(fn (array $record): string => (string) ($record['artifact_truth_color'] ?? 'gray'))
                    ->icon(fn (array $record): ?string => is_string($record['artifact_truth_icon'] ?? null) ? $record['artifact_truth_icon'] : null)
                    ->description(fn (array $record): ?string => is_string($record['artifact_truth_explanation'] ?? null) ? $record['artifact_truth_explanation'] : null)
                    ->sortable()
                    ->wrap(),
                TextColumn::make('generated_at')
                    ->label('Generated')
                    ->placeholder('—')
                    ->sortable(),
                TextColumn::make('next_step')
                    ->label('Next step')
                    ->wrap(),
            ])
            ->recordUrl(fn ($record): ?string => is_array($record) ? (is_string($record['view_url'] ?? null) ? $record['view_url'] : null) : null)
            ->actions([])
            ->bulkActions([])
            ->emptyStateHeading('No evidence snapshots in this scope')
            ->emptyStateDescription(fn (): string => $this->hasActiveOverviewFilters()
                ? 'Clear the current filters to return to the full workspace evidence overview.'
                : 'Adjust filters or create an environment snapshot to populate the workspace overview.')
            ->emptyStateActions([
                Action::make('clear_filters')
                    ->label('Clear filters')
                    ->icon('heroicon-o-x-mark')
                    ->color('gray')
                    ->visible(fn (): bool => $this->hasActiveOverviewFilters())
                    ->action(fn (): mixed => $this->clearOverviewFilters()),
            ]);
    }

    /**
     * @return array<Action>
     */
    protected function getHeaderActions(): array
    {
        return [
            Action::make('clear_filters')
                ->label('Clear filters')
                ->color('gray')
                ->visible(fn (): bool => $this->hasActiveOverviewFilters())
                ->action(fn (): mixed => $this->clearOverviewFilters()),
        ];
    }

    public function clearOverviewFilters(): void
    {
        $this->tableFilters = [
            'managed_environment_id' => ['value' => null],
        ];
        $this->tableDeferredFilters = $this->tableFilters;
        $this->tableSearch = '';
        $this->rows = $this->rowsForState($this->tableFilters, $this->tableSearch)->values()->all();

        session()->forget($this->getTableFiltersSessionKey());
        session()->put($this->getTableSearchSessionKey(), $this->tableSearch);
        $this->clearWorkspaceHubEnvironmentFilterState(request());

        $this->redirectToCleanWorkspaceHubUrl($this->overviewUrl(), request());
    }

    /**
     * @return array{label: string, clear_url: string}|null
     */
    public function environmentFilterChip(): ?array
    {
        $tenant = $this->filteredTenant();

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

        return [
            'label' => (string) $tenant->name,
            'clear_url' => $this->cleanWorkspaceHubUrl($this->overviewUrl()),
        ];
    }

    private function snapshotTruth(EvidenceSnapshot $snapshot, bool $fresh = false): ArtifactTruthEnvelope
    {
        $presenter = app(ArtifactTruthPresenter::class);

        return $fresh
            ? $presenter->forEvidenceSnapshotFresh($snapshot)
            : $presenter->forEvidenceSnapshot($snapshot);
    }

    private function snapshotOutcome(EvidenceSnapshot $snapshot, bool $fresh = false): CompressedGovernanceOutcome
    {
        $presenter = app(ArtifactTruthPresenter::class);

        return $presenter->compressedOutcomeFor($snapshot, SurfaceCompressionContext::evidenceOverview(), $fresh)
            ?? $presenter->compressedOutcomeFromEnvelope(
                $this->snapshotTruth($snapshot, $fresh),
                SurfaceCompressionContext::evidenceOverview(),
            );
    }

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

        if (! $user instanceof User) {
            throw new AuthenticationException;
        }

        app(WorkspaceContext::class)->currentWorkspaceForMemberOrFail($user, request());
    }

    /**
     * @return array<int, ManagedEnvironment>
     */
    private function accessibleTenants(): array
    {
        if (is_array($this->accessibleTenants)) {
            return $this->accessibleTenants;
        }

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

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

        $workspaceId = $this->workspaceId();

        return $this->accessibleTenants = $user->accessibleManagedEnvironmentsQuery($workspaceId)
            ->orderBy('managed_environments.name')
            ->get()
            ->filter(fn (ManagedEnvironment $tenant): bool => (int) $tenant->workspace_id === $workspaceId && $user->can('evidence.view', $tenant))
            ->values()
            ->all();
    }

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

    /**
     * @param  array<string, mixed>  $filters
     * @return Collection<string, array<string, mixed>>
     */
    private function rowsForState(array $filters = [], ?string $search = null): Collection
    {
        $rows = $this->baseRows();
        $tenantFilter = $this->normalizeTenantFilter($filters['managed_environment_id']['value'] ?? data_get($this->tableFilters, 'managed_environment_id.value'));
        $normalizedSearch = Str::lower(trim((string) ($search ?? $this->tableSearch)));

        if ($tenantFilter !== null) {
            $rows = $rows->where('managed_environment_id', $tenantFilter);
        }

        if ($normalizedSearch === '') {
            return $rows;
        }

        return $rows->filter(function (array $row) use ($normalizedSearch): bool {
            $haystack = implode(' ', [
                (string) ($row['tenant_name'] ?? ''),
                (string) ($row['artifact_truth_label'] ?? ''),
                (string) ($row['artifact_truth_explanation'] ?? ''),
                (string) ($row['freshness_label'] ?? ''),
                (string) ($row['next_step'] ?? ''),
            ]);

            return str_contains(Str::lower($haystack), $normalizedSearch);
        });
    }

    /**
     * @return Collection<string, array<string, mixed>>
     */
    private function baseRows(): Collection
    {
        $snapshots = $this->latestAccessibleSnapshots();
        $currentReviewTenantIds = $this->currentReviewTenantIds($snapshots);

        return $snapshots->mapWithKeys(function (EvidenceSnapshot $snapshot) use ($currentReviewTenantIds): array {
            return [(string) $snapshot->getKey() => $this->rowForSnapshot($snapshot, $currentReviewTenantIds)];
        });
    }

    /**
     * @return Collection<int, EvidenceSnapshot>
     */
    private function latestAccessibleSnapshots(): Collection
    {
        if ($this->cachedSnapshots instanceof Collection) {
            return $this->cachedSnapshots;
        }

        $tenantIds = collect($this->accessibleTenants())
            ->map(static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey())
            ->all();

        $query = EvidenceSnapshot::query()
            ->with('tenant')
            ->where('workspace_id', $this->workspaceId())
            ->where('status', 'active')
            ->latest('generated_at');

        if ($tenantIds === []) {
            $query->whereRaw('1 = 0');
        } else {
            $query->whereIn('managed_environment_id', $tenantIds);
        }

        return $this->cachedSnapshots = $query->get()->unique('managed_environment_id')->values();
    }

    /**
     * @param  Collection<int, EvidenceSnapshot>  $snapshots
     * @return array<int, bool>
     */
    private function currentReviewTenantIds(Collection $snapshots): array
    {
        return EnvironmentReview::query()
            ->where('workspace_id', $this->workspaceId())
            ->whereIn('managed_environment_id', $snapshots->pluck('managed_environment_id')->map(static fn (mixed $tenantId): int => (int) $tenantId)->all())
            ->whereIn('status', [
                EnvironmentReviewStatus::Draft->value,
                EnvironmentReviewStatus::Ready->value,
                EnvironmentReviewStatus::Published->value,
            ])
            ->pluck('managed_environment_id')
            ->mapWithKeys(static fn (mixed $tenantId): array => [(int) $tenantId => true])
            ->all();
    }

    /**
     * @param  array<int, bool>  $currentReviewTenantIds
     * @return array<string, mixed>
     */
    private function rowForSnapshot(EvidenceSnapshot $snapshot, array $currentReviewTenantIds): array
    {
        $truth = $this->snapshotTruth($snapshot);
        $outcome = $this->snapshotOutcome($snapshot);
        $tenantId = (int) $snapshot->managed_environment_id;
        $hasCurrentReview = $currentReviewTenantIds[$tenantId] ?? false;
        $nextStep = ! $hasCurrentReview && $truth->contentState === 'trusted' && $truth->freshnessState === 'current'
            ? 'Create a current review from this evidence snapshot'
            : $outcome->nextActionText;

        return [
            'tenant_name' => $snapshot->tenant?->name ?? 'Unknown tenant',
            'managed_environment_id' => $tenantId,
            'snapshot_id' => (int) $snapshot->getKey(),
            'generated_at' => $snapshot->generated_at?->toDateTimeString(),
            'artifact_truth_label' => $outcome->primaryLabel,
            'artifact_truth_color' => $outcome->primaryBadge->color,
            'artifact_truth_icon' => $outcome->primaryBadge->icon,
            'artifact_truth_explanation' => $outcome->primaryReason,
            'artifact_truth' => [
                'label' => $outcome->primaryLabel,
                'color' => $outcome->primaryBadge->color,
                'icon' => $outcome->primaryBadge->icon,
                'explanation' => $outcome->primaryReason,
            ],
            'next_step' => $nextStep,
            'view_url' => $snapshot->tenant
                ? EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $snapshot->tenant)
                : null,
        ];
    }

    /**
     * @param  Collection<string, array<string, mixed>>  $rows
     * @return Collection<string, array<string, mixed>>
     */
    private function sortRows(Collection $rows, ?string $sortColumn, ?string $sortDirection): Collection
    {
        $sortColumn = in_array($sortColumn, ['tenant_name', 'artifact_truth_label', 'generated_at'], true)
            ? $sortColumn
            : 'tenant_name';
        $descending = Str::lower((string) ($sortDirection ?? 'asc')) === 'desc';

        $records = $rows->all();

        uasort($records, static function (array $left, array $right) use ($sortColumn, $descending): int {
            $comparison = strnatcasecmp((string) ($left[$sortColumn] ?? ''), (string) ($right[$sortColumn] ?? ''));

            if ($comparison === 0) {
                $comparison = strnatcasecmp((string) ($left['tenant_name'] ?? ''), (string) ($right['tenant_name'] ?? ''));
            }

            return $descending ? ($comparison * -1) : $comparison;
        });

        return collect($records);
    }

    /**
     * @param  Collection<string, array<string, mixed>>  $rows
     */
    private function paginateRows(Collection $rows, int $page, int $recordsPerPage): LengthAwarePaginator
    {
        return new LengthAwarePaginator(
            items: $rows->forPage($page, $recordsPerPage),
            total: $rows->count(),
            perPage: $recordsPerPage,
            currentPage: $page,
        );
    }

    private function seedTableStateFromQuery(): void
    {
        $query = request()->query();

        if (array_key_exists('search', $query)) {
            $this->tableSearch = trim((string) request()->query('search', ''));
        }

        if (! array_key_exists('environment_id', $query)) {
            return;
        }

        $workspace = $this->workspace();

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

        $filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);

        if (! $filter instanceof WorkspaceHubEnvironmentFilter) {
            return;
        }

        $tenantFilter = $this->normalizeTenantFilter($filter->environmentId());

        if ($tenantFilter === null) {
            throw new NotFoundHttpException;
        }

        $this->tableFilters = [
            'managed_environment_id' => ['value' => (string) $tenantFilter],
        ];
        $this->tableDeferredFilters = $this->tableFilters;
    }

    private function normalizeTenantFilter(mixed $value): ?int
    {
        if (! is_numeric($value)) {
            return null;
        }

        $requestedTenantId = (int) $value;
        $allowedTenantIds = collect($this->accessibleTenants())
            ->map(static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey())
            ->all();

        return in_array($requestedTenantId, $allowedTenantIds, true)
            ? $requestedTenantId
            : null;
    }

    private function hasActiveOverviewFilters(): bool
    {
        return filled(data_get($this->tableFilters, 'managed_environment_id.value'))
            || trim((string) $this->tableSearch) !== '';
    }

    private function overviewUrl(array $overrides = []): string
    {
        return route(
            'admin.evidence.overview',
            array_filter($overrides, static fn (mixed $value): bool => $value !== null && $value !== '' && $value !== []),
        );
    }

    private function workspaceId(): int
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            throw new AuthenticationException;
        }

        return (int) app(WorkspaceContext::class)
            ->currentWorkspaceForMemberOrFail($user, request())
            ->getKey();
    }

    private function workspace(): ?Workspace
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            throw new AuthenticationException;
        }

        return app(WorkspaceContext::class)->currentWorkspaceForMemberOrFail($user, request());
    }

    private function filteredTenant(): ?ManagedEnvironment
    {
        $tenantId = $this->normalizeTenantFilter(data_get($this->tableFilters, 'managed_environment_id.value'));

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

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

        return null;
    }
}
