<?php

declare(strict_types=1);

namespace App\Filament\Pages;

use App\Filament\Clusters\Inventory\InventoryCluster;
use App\Filament\Widgets\Inventory\InventoryKpiHeader;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Auth\CapabilityResolver;
use App\Services\Inventory\CoverageCapabilitiesResolver;
use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeCatalog;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Badges\TagBadgeCatalog;
use App\Support\Badges\TagBadgeDomain;
use App\Support\Badges\TagBadgeRenderer;
use App\Support\Inventory\InventoryPolicyTypeMeta;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Pages\Page;
use Filament\Support\Enums\FontFamily;
use Filament\Tables\Columns\IconColumn;
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 UnitEnum;

class InventoryCoverage extends Page implements HasTable
{
    use InteractsWithTable;

    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-table-cells';

    protected static ?int $navigationSort = 3;

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

    protected static ?string $navigationLabel = 'Coverage';

    protected static ?string $cluster = InventoryCluster::class;

    protected string $view = 'filament.pages.inventory-coverage';

    public static function canAccess(): bool
    {
        $tenant = Tenant::current();
        $user = auth()->user();

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

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

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

    public function mount(): void
    {
        $this->mountInteractsWithTable();
    }

    protected function getHeaderWidgets(): array
    {
        return [
            InventoryKpiHeader::class,
        ];
    }

    public function table(Table $table): Table
    {
        return $table
            ->searchable()
            ->searchPlaceholder('Search by policy type or label')
            ->defaultSort('label')
            ->defaultPaginationPageOption(50)
            ->paginated(\App\Support\Filament\TablePaginationProfiles::customPage())
            ->records(function (
                ?string $sortColumn,
                ?string $sortDirection,
                ?string $search,
                array $filters,
                int $page,
                int $recordsPerPage
            ): LengthAwarePaginator {
                $rows = $this->filterRows(
                    rows: $this->coverageRows(),
                    search: $search,
                    filters: $filters,
                );

                $rows = $this->sortRows(
                    rows: $rows,
                    sortColumn: $sortColumn,
                    sortDirection: $sortDirection,
                );

                return $this->paginateRows(
                    rows: $rows,
                    page: $page,
                    recordsPerPage: $recordsPerPage,
                );
            })
            ->columns([
                TextColumn::make('type')
                    ->label('Type')
                    ->sortable()
                    ->fontFamily(FontFamily::Mono)
                    ->copyable()
                    ->wrap(),
                TextColumn::make('label')
                    ->label('Label')
                    ->sortable()
                    ->badge()
                    ->formatStateUsing(function (?string $state, array $record): string {
                        return TagBadgeCatalog::spec(
                            TagBadgeDomain::PolicyType,
                            $record['type'] ?? $state,
                        )->label;
                    })
                    ->color(function (?string $state, array $record): string {
                        return TagBadgeCatalog::spec(
                            TagBadgeDomain::PolicyType,
                            $record['type'] ?? $state,
                        )->color;
                    })
                    ->icon(function (?string $state, array $record): ?string {
                        return TagBadgeCatalog::spec(
                            TagBadgeDomain::PolicyType,
                            $record['type'] ?? $state,
                        )->icon;
                    })
                    ->iconColor(function (?string $state, array $record): ?string {
                        $spec = TagBadgeCatalog::spec(
                            TagBadgeDomain::PolicyType,
                            $record['type'] ?? $state,
                        );

                        return $spec->iconColor ?? $spec->color;
                    })
                    ->wrap(),
                TextColumn::make('risk')
                    ->label('Risk')
                    ->badge()
                    ->formatStateUsing(BadgeRenderer::label(BadgeDomain::PolicyRisk))
                    ->color(BadgeRenderer::color(BadgeDomain::PolicyRisk))
                    ->icon(BadgeRenderer::icon(BadgeDomain::PolicyRisk))
                    ->iconColor(BadgeRenderer::iconColor(BadgeDomain::PolicyRisk)),
                TextColumn::make('restore')
                    ->label('Restore')
                    ->badge()
                    ->state(fn (array $record): ?string => $record['restore'])
                    ->formatStateUsing(function (?string $state): string {
                        return filled($state)
                            ? BadgeCatalog::spec(BadgeDomain::PolicyRestoreMode, $state)->label
                            : 'Not provided';
                    })
                    ->color(function (?string $state): string {
                        return filled($state)
                            ? BadgeCatalog::spec(BadgeDomain::PolicyRestoreMode, $state)->color
                            : 'gray';
                    })
                    ->icon(function (?string $state): ?string {
                        return filled($state)
                            ? BadgeCatalog::spec(BadgeDomain::PolicyRestoreMode, $state)->icon
                            : 'heroicon-m-minus-circle';
                    })
                    ->iconColor(function (?string $state): ?string {
                        if (! filled($state)) {
                            return 'gray';
                        }

                        $spec = BadgeCatalog::spec(BadgeDomain::PolicyRestoreMode, $state);

                        return $spec->iconColor ?? $spec->color;
                    }),
                TextColumn::make('category')
                    ->badge()
                    ->formatStateUsing(TagBadgeRenderer::label(TagBadgeDomain::PolicyCategory))
                    ->color(TagBadgeRenderer::color(TagBadgeDomain::PolicyCategory))
                    ->icon(TagBadgeRenderer::icon(TagBadgeDomain::PolicyCategory))
                    ->iconColor(TagBadgeRenderer::iconColor(TagBadgeDomain::PolicyCategory))
                    ->toggleable()
                    ->wrap(),
                TextColumn::make('segment')
                    ->label('Segment')
                    ->badge()
                    ->formatStateUsing(fn (?string $state): string => $state === 'foundation' ? 'Foundation' : 'Policy')
                    ->color(fn (?string $state): string => $state === 'foundation' ? 'gray' : 'info')
                    ->toggleable(),
                IconColumn::make('dependencies')
                    ->label('Dependencies')
                    ->boolean()
                    ->trueIcon('heroicon-m-check-circle')
                    ->falseIcon('heroicon-m-minus-circle')
                    ->trueColor('success')
                    ->falseColor('gray')
                    ->alignCenter()
                    ->toggleable(),
            ])
            ->filters($this->tableFilters())
            ->emptyStateHeading('No coverage entries match this view')
            ->emptyStateDescription('Clear the current search or filters to return to the full coverage matrix.')
            ->emptyStateIcon('heroicon-o-funnel')
            ->emptyStateActions([
                Action::make('clear_filters')
                    ->label('Clear filters')
                    ->icon('heroicon-o-x-mark')
                    ->color('gray')
                    ->action(function (): void {
                        $this->resetTable();
                    }),
            ])
            ->actions([])
            ->bulkActions([]);
    }

    /**
     * @return array<int, SelectFilter>
     */
    protected function tableFilters(): array
    {
        $filters = [
            SelectFilter::make('category')
                ->label('Category')
                ->options($this->categoryFilterOptions()),
        ];

        if ($this->restoreFilterOptions() !== []) {
            $filters[] = SelectFilter::make('restore')
                ->label('Restore mode')
                ->options($this->restoreFilterOptions());
        }

        return $filters;
    }

    /**
     * @return Collection<string, array{
     *     __key: string,
     *     key: string,
     *     segment: string,
     *     type: string,
     *     label: string,
     *     category: string,
     *     dependencies: bool,
     *     restore: ?string,
     *     risk: string,
     *     source_order: int
     * }>
     */
    protected function coverageRows(): Collection
    {
        $resolver = app(CoverageCapabilitiesResolver::class);

        $supported = $this->mapCoverageRows(
            rows: InventoryPolicyTypeMeta::supported(),
            segment: 'policy',
            sourceOrderOffset: 0,
            resolver: $resolver,
        );

        return $supported->merge($this->mapCoverageRows(
            rows: InventoryPolicyTypeMeta::foundations(),
            segment: 'foundation',
            sourceOrderOffset: $supported->count(),
            resolver: $resolver,
        ));
    }

    /**
     * @param  array<int, array<string, mixed>>  $rows
     * @return Collection<string, array{
     *     __key: string,
     *     key: string,
     *     segment: string,
     *     type: string,
     *     label: string,
     *     category: string,
     *     dependencies: bool,
     *     restore: ?string,
     *     risk: string,
     *     source_order: int
     * }>
     */
    protected function mapCoverageRows(
        array $rows,
        string $segment,
        int $sourceOrderOffset,
        CoverageCapabilitiesResolver $resolver
    ): Collection {
        return collect($rows)
            ->values()
            ->mapWithKeys(function (array $row, int $index) use ($resolver, $segment, $sourceOrderOffset): array {
                $type = (string) ($row['type'] ?? '');

                if ($type === '') {
                    return [];
                }

                $key = "{$segment}:{$type}";
                $restore = $row['restore'] ?? null;
                $risk = $row['risk'] ?? 'n/a';

                return [
                    $key => [
                        '__key' => $key,
                        'key' => $key,
                        'segment' => $segment,
                        'type' => $type,
                        'label' => (string) ($row['label'] ?? $type),
                        'category' => (string) ($row['category'] ?? 'Other'),
                        'dependencies' => $segment === 'policy' && $resolver->supportsDependencies($type),
                        'restore' => is_string($restore) ? $restore : null,
                        'risk' => is_string($risk) ? $risk : 'n/a',
                        'source_order' => $sourceOrderOffset + $index,
                    ],
                ];
            });
    }

    /**
     * @param  Collection<string, array<string, mixed>>  $rows
     * @param  array<string, mixed>  $filters
     * @return Collection<string, array<string, mixed>>
     */
    protected function filterRows(Collection $rows, ?string $search, array $filters): Collection
    {
        $normalizedSearch = Str::lower(trim((string) $search));
        $category = $filters['category']['value'] ?? null;
        $restore = $filters['restore']['value'] ?? null;

        return $rows
            ->when(
                $normalizedSearch !== '',
                function (Collection $rows) use ($normalizedSearch): Collection {
                    return $rows->filter(function (array $row) use ($normalizedSearch): bool {
                        return str_contains(Str::lower((string) $row['type']), $normalizedSearch)
                            || str_contains(Str::lower((string) $row['label']), $normalizedSearch);
                    });
                },
            )
            ->when(
                filled($category),
                fn (Collection $rows): Collection => $rows->where('category', (string) $category),
            )
            ->when(
                filled($restore),
                fn (Collection $rows): Collection => $rows->where('restore', (string) $restore),
            );
    }

    /**
     * @param  Collection<string, array<string, mixed>>  $rows
     * @return Collection<string, array<string, mixed>>
     */
    protected function sortRows(Collection $rows, ?string $sortColumn, ?string $sortDirection): Collection
    {
        $sortColumn = in_array($sortColumn, ['type', 'label'], true) ? $sortColumn : null;

        if ($sortColumn === null) {
            return $rows->sortBy('source_order');
        }

        $records = $rows->all();

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

            if ($comparison === 0) {
                $comparison = ((int) ($left['source_order'] ?? 0)) <=> ((int) ($right['source_order'] ?? 0));
            }

            return $sortDirection === 'desc' ? ($comparison * -1) : $comparison;
        });

        return collect($records);
    }

    /**
     * @param  Collection<string, array<string, mixed>>  $rows
     */
    protected function paginateRows(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>
     */
    protected function categoryFilterOptions(): array
    {
        return $this->coverageRows()
            ->pluck('category')
            ->filter(fn (mixed $category): bool => is_string($category) && $category !== '')
            ->unique()
            ->sort()
            ->mapWithKeys(function (string $category): array {
                return [
                    $category => TagBadgeCatalog::spec(TagBadgeDomain::PolicyCategory, $category)->label,
                ];
            })
            ->all();
    }

    /**
     * @return array<string, string>
     */
    protected function restoreFilterOptions(): array
    {
        return $this->coverageRows()
            ->pluck('restore')
            ->filter(fn (mixed $restore): bool => is_string($restore) && $restore !== '')
            ->unique()
            ->sort()
            ->mapWithKeys(function (string $restore): array {
                return [
                    $restore => BadgeCatalog::spec(BadgeDomain::PolicyRestoreMode, $restore)->label,
                ];
            })
            ->all();
    }
}
