<?php

declare(strict_types=1);

namespace App\Filament\Pages;

use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\WorkspaceMembership;
use App\Services\Intune\ManagedEnvironmentRequiredPermissionsViewModelBuilder;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\ManagedEnvironmentLinks;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Workspaces\WorkspaceContext;
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\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\Attributes\Locked;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class EnvironmentRequiredPermissions extends Page implements HasTable
{
    use InteractsWithTable;

    protected static bool $isDiscovered = false;

    protected static bool $shouldRegisterNavigation = false;

    protected static ?string $slug = 'workspaces/{workspace}/environments/{environment}/required-permissions';

    protected static ?string $title = 'Required permissions';

    protected string $view = 'filament.pages.environment-required-permissions';

    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly)
            ->exempt(ActionSurfaceSlot::ListHeader, 'Required permissions keeps guidance, copy flows, and filter reset actions inside body sections instead of page header actions.')
            ->exempt(ActionSurfaceSlot::InspectAffordance, 'Required permissions rows are reviewed inline inside the diagnostic matrix and do not open a separate inspect destination.')
            ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'Permission rows are read-only and do not expose row-level secondary actions.')
            ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'Required permissions does not expose bulk actions.')
            ->satisfy(ActionSurfaceSlot::ListEmptyState, 'The inline permissions matrix provides purposeful no-data, all-clear, and no-matches states with verification or reset guidance.');
    }

    #[Locked]
    public ?int $scopedTenantId = null;

    /**
     * @var array<string, mixed>|null
     */
    private ?array $cachedViewModel = null;

    private ?string $cachedViewModelStateKey = null;

    public static function canAccess(): bool
    {
        return static::hasScopedTenantAccess(static::resolveScopedTenant());
    }

    public function currentTenant(): ?ManagedEnvironment
    {
        return $this->trustedScopedTenant();
    }

    public function mount(ManagedEnvironment|string|null $environment = null): void
    {
        $tenant = static::resolveScopedTenant($environment);

        if (! $tenant instanceof ManagedEnvironment || ! static::hasScopedTenantAccess($tenant)) {
            abort(404);
        }

        $this->scopedTenantId = (int) $tenant->getKey();
        $this->heading = $tenant->getFilamentName();
        $this->subheading = 'Required permissions';

        $this->seedTableStateFromQuery();
        $this->mountInteractsWithTable();
    }

    public function table(Table $table): Table
    {
        return $table
            ->defaultSort('sort_priority')
            ->defaultPaginationPageOption(25)
            ->paginated(TablePaginationProfiles::customPage())
            ->persistFiltersInSession()
            ->persistSearchInSession()
            ->persistSortInSession()
            ->searchable()
            ->searchPlaceholder('Search permission key or description…')
            ->records(function (
                ?string $sortColumn,
                ?string $sortDirection,
                ?string $search,
                array $filters,
                int $page,
                int $recordsPerPage
            ): LengthAwarePaginator {
                $state = $this->filterState(filters: $filters, search: $search);
                $rows = $this->permissionRowsForState($state);
                $rows = $this->sortPermissionRows($rows, $sortColumn, $sortDirection);

                return $this->paginatePermissionRows($rows, $page, $recordsPerPage);
            })
            ->filters([
                SelectFilter::make('status')
                    ->label('Status')
                    ->default('missing')
                    ->options([
                        'missing' => 'Missing',
                        'present' => 'Present',
                        'all' => 'All',
                    ]),
                SelectFilter::make('type')
                    ->label('Type')
                    ->default('all')
                    ->options([
                        'all' => 'All',
                        'application' => 'Application',
                        'delegated' => 'Delegated',
                    ]),
                SelectFilter::make('features')
                    ->label('Features')
                    ->multiple()
                    ->options(fn (): array => $this->featureFilterOptions())
                    ->searchable(),
            ])
            ->columns([
                TextColumn::make('key')
                    ->label('Permission')
                    ->description(fn (array $record): ?string => is_string($record['description'] ?? null) ? $record['description'] : null)
                    ->wrap()
                    ->sortable(),
                TextColumn::make('type_label')
                    ->label('Type')
                    ->badge()
                    ->color('gray')
                    ->sortable(),
                TextColumn::make('status')
                    ->label('Status')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::ManagedEnvironmentPermissionStatus))
                    ->color(BadgeRenderer::color(BadgeDomain::ManagedEnvironmentPermissionStatus))
                    ->icon(BadgeRenderer::icon(BadgeDomain::ManagedEnvironmentPermissionStatus))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::ManagedEnvironmentPermissionStatus))
                    ->sortable(),
                TextColumn::make('features_label')
                    ->label('Features')
                    ->wrap()
                    ->toggleable(),
            ])
            ->actions([])
            ->bulkActions([])
            ->emptyStateHeading(fn (): string => $this->permissionsEmptyStateHeading())
            ->emptyStateDescription(fn (): string => $this->permissionsEmptyStateDescription())
            ->emptyStateActions([
                Action::make('clear_filters')
                    ->label('Clear filters')
                    ->icon('heroicon-o-x-mark')
                    ->color('gray')
                    ->visible(fn (): bool => $this->hasActivePermissionFilters())
                    ->action(fn (): mixed => $this->clearPermissionFilters()),
            ]);
    }

    /**
     * @return array<string, mixed>
     */
    public function viewModel(): array
    {
        return $this->viewModelForState($this->filterState());
    }

    public function clearPermissionFilters(): void
    {
        $this->tableFilters = [
            'status' => ['value' => 'missing'],
            'type' => ['value' => 'all'],
            'features' => ['values' => []],
        ];
        $this->tableDeferredFilters = $this->tableFilters;
        $this->tableSearch = '';
        $this->cachedViewModel = null;
        $this->cachedViewModelStateKey = null;

        session()->put($this->getTableFiltersSessionKey(), $this->tableFilters);
        session()->put($this->getTableSearchSessionKey(), $this->tableSearch);

        $this->resetPage();
    }

    public function reRunVerificationUrl(): string
    {
        $tenant = $this->trustedScopedTenant();

        if ($tenant instanceof ManagedEnvironment) {
            return ManagedEnvironmentLinks::viewUrl($tenant);
        }

        return route('admin.onboarding');
    }

    public function manageProviderConnectionUrl(): ?string
    {
        $tenant = $this->trustedScopedTenant();

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

        return ManagedEnvironmentLinks::providerConnectionsUrl($tenant);
    }

    protected static function resolveScopedTenant(ManagedEnvironment|string|null $tenant = null): ?ManagedEnvironment
    {
        if ($tenant instanceof ManagedEnvironment) {
            return $tenant;
        }

        if (is_string($tenant) && $tenant !== '') {
            return ManagedEnvironment::query()
                ->where('slug', $tenant)
                ->first();
        }

        $routeTenant = request()->route('environment') ?? request()->route('tenant');

        if ($routeTenant instanceof ManagedEnvironment) {
            return $routeTenant;
        }

        if (is_string($routeTenant) && $routeTenant !== '') {
            return ManagedEnvironment::query()
                ->where('slug', $routeTenant)
                ->first();
        }

        $queryTenant = request()->query('tenant');

        if (is_string($queryTenant) && $queryTenant !== '') {
            return ManagedEnvironment::query()
                ->where('slug', $queryTenant)
                ->first();
        }

        return null;
    }

    private static function hasScopedTenantAccess(?ManagedEnvironment $tenant): bool
    {
        $user = auth()->user();

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

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

        if ($workspaceId === null || (int) $tenant->workspace_id !== (int) $workspaceId) {
            return false;
        }

        $isWorkspaceMember = WorkspaceMembership::query()
            ->where('workspace_id', (int) $workspaceId)
            ->where('user_id', (int) $user->getKey())
            ->exists();

        if (! $isWorkspaceMember) {
            return false;
        }

        return $user->canAccessTenant($tenant);
    }

    private function trustedScopedTenant(): ?ManagedEnvironment
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            return null;
        }

        $workspaceContext = app(WorkspaceContext::class);

        try {
            $workspace = $workspaceContext->currentWorkspaceForMemberOrFail($user, request());
        } catch (NotFoundHttpException) {
            return null;
        }

        $routeTenant = static::resolveScopedTenant();

        if ($routeTenant instanceof ManagedEnvironment) {
            try {
                return $workspaceContext->ensureTenantAccessibleInCurrentWorkspace($routeTenant, $user, request());
            } catch (NotFoundHttpException) {
                return null;
            }
        }

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

        $tenant = ManagedEnvironment::query()->withTrashed()->whereKey($this->scopedTenantId)->first();

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

        try {
            return $workspaceContext->ensureTenantAccessibleInCurrentWorkspace($tenant, $user, request());
        } catch (NotFoundHttpException) {
            return null;
        }
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return array{status:'missing'|'present'|'all',type:'application'|'delegated'|'all',features:array<int, string>,search:string}
     */
    private function filterState(array $filters = [], ?string $search = null): array
    {
        return ManagedEnvironmentRequiredPermissionsViewModelBuilder::normalizeFilterState([
            'status' => $filters['status']['value'] ?? data_get($this->tableFilters, 'status.value'),
            'type' => $filters['type']['value'] ?? data_get($this->tableFilters, 'type.value'),
            'features' => $filters['features']['values'] ?? data_get($this->tableFilters, 'features.values', []),
            'search' => $search ?? $this->tableSearch,
        ]);
    }

    /**
     * @param  array{status:'missing'|'present'|'all',type:'application'|'delegated'|'all',features:array<int, string>,search:string}  $state
     * @return array<string, mixed>
     */
    private function viewModelForState(array $state): array
    {
        $tenant = $this->trustedScopedTenant();

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

        $stateKey = json_encode([$tenant->getKey(), $state]);

        if ($this->cachedViewModelStateKey === $stateKey && is_array($this->cachedViewModel)) {
            return $this->cachedViewModel;
        }

        $builder = app(ManagedEnvironmentRequiredPermissionsViewModelBuilder::class);

        $this->cachedViewModelStateKey = $stateKey ?: null;
        $this->cachedViewModel = $builder->build($tenant, $state);

        return $this->cachedViewModel;
    }

    /**
     * @return Collection<string, array<string, mixed>>
     */
    private function permissionRowsForState(array $state): Collection
    {
        return collect($this->viewModelForState($state)['permissions'] ?? [])
            ->filter(fn (mixed $row): bool => is_array($row) && is_string($row['key'] ?? null))
            ->mapWithKeys(function (array $row): array {
                $key = (string) $row['key'];

                return [
                    $key => [
                        'key' => $key,
                        'description' => is_string($row['description'] ?? null) ? $row['description'] : null,
                        'type' => (string) ($row['type'] ?? 'application'),
                        'type_label' => ($row['type'] ?? 'application') === 'delegated' ? 'Delegated' : 'Application',
                        'status' => (string) ($row['status'] ?? 'missing'),
                        'features_label' => implode(', ', array_filter((array) ($row['features'] ?? []), 'is_string')),
                        'sort_priority' => $this->statusSortWeight((string) ($row['status'] ?? 'missing')),
                    ],
                ];
            });
    }

    /**
     * @param  Collection<string, array<string, mixed>>  $rows
     * @return Collection<string, array<string, mixed>>
     */
    private function sortPermissionRows(Collection $rows, ?string $sortColumn, ?string $sortDirection): Collection
    {
        $sortColumn = in_array($sortColumn, ['sort_priority', 'key', 'type_label', 'status', 'features_label'], true)
            ? $sortColumn
            : 'sort_priority';
        $descending = Str::lower((string) ($sortDirection ?? 'asc')) === 'desc';

        $records = $rows->all();

        uasort($records, static function (array $left, array $right) use ($sortColumn, $descending): int {
            $comparison = match ($sortColumn) {
                'sort_priority' => ((int) ($left['sort_priority'] ?? 0)) <=> ((int) ($right['sort_priority'] ?? 0)),
                default => strnatcasecmp(
                    (string) ($left[$sortColumn] ?? ''),
                    (string) ($right[$sortColumn] ?? ''),
                ),
            };

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

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

        return collect($records);
    }

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

    /**
     * @return array<string, string>
     */
    private function featureFilterOptions(): array
    {
        return collect(data_get($this->viewModelForState([
            'status' => 'all',
            'type' => 'all',
            'features' => [],
            'search' => '',
        ]), 'overview.feature_impacts', []))
            ->filter(fn (mixed $impact): bool => is_array($impact) && is_string($impact['feature'] ?? null))
            ->mapWithKeys(fn (array $impact): array => [
                (string) $impact['feature'] => (string) $impact['feature'],
            ])
            ->all();
    }

    private function permissionsEmptyStateHeading(): string
    {
        $viewModel = $this->viewModel();
        $counts = is_array(data_get($viewModel, 'overview.counts')) ? data_get($viewModel, 'overview.counts') : [];
        $state = $this->filterState();
        $allPermissions = data_get($this->viewModelForState([
            'status' => 'all',
            'type' => 'all',
            'features' => [],
            'search' => '',
        ]), 'permissions', []);

        $missingTotal = (int) ($counts['missing_application'] ?? 0)
            + (int) ($counts['missing_delegated'] ?? 0)
            + (int) ($counts['error'] ?? 0);
        $requiredTotal = $missingTotal + (int) ($counts['present'] ?? 0);

        if (! is_array($allPermissions) || $allPermissions === []) {
            return 'No permissions configured';
        }

        if ($state['status'] === 'missing' && $missingTotal === 0 && $state['type'] === 'all' && $state['features'] === [] && trim($state['search']) === '') {
            return 'All required permissions are present';
        }

        return 'No matches';
    }

    private function permissionsEmptyStateDescription(): string
    {
        return match ($this->permissionsEmptyStateHeading()) {
            'No permissions configured' => 'No required permissions are currently configured in config/intune_permissions.php.',
            'All required permissions are present' => 'Switch Status to All if you want to review the full matrix.',
            default => 'No permissions match the current filters.',
        };
    }

    private function hasActivePermissionFilters(): bool
    {
        $state = $this->filterState();

        return $state['status'] !== 'missing'
            || $state['type'] !== 'all'
            || $state['features'] !== []
            || trim($state['search']) !== '';
    }

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

        if (! array_key_exists('status', $query) && ! array_key_exists('type', $query) && ! array_key_exists('features', $query) && ! array_key_exists('search', $query)) {
            return;
        }

        $queryFeatures = request()->query('features', []);

        $state = ManagedEnvironmentRequiredPermissionsViewModelBuilder::normalizeFilterState([
            'status' => request()->query('status', 'missing'),
            'type' => request()->query('type', 'all'),
            'features' => is_array($queryFeatures) ? $queryFeatures : [],
            'search' => request()->query('search', ''),
        ]);

        $this->tableFilters = [
            'status' => ['value' => $state['status']],
            'type' => ['value' => $state['type']],
            'features' => ['values' => $state['features']],
        ];
        $this->tableDeferredFilters = $this->tableFilters;
        $this->tableSearch = $state['search'];
    }

    private function statusSortWeight(string $status): int
    {
        return match ($status) {
            'missing' => 0,
            'error' => 1,
            default => 2,
        };
    }
}
