<?php

declare(strict_types=1);

namespace App\Livewire;

use App\Models\InventoryItem;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Auth\CapabilityResolver;
use App\Services\Inventory\DependencyQueryService;
use App\Services\Inventory\DependencyTargets\DependencyTargetResolver;
use App\Support\Auth\Capabilities;
use App\Support\Enums\RelationshipType;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Filament\Tables\TableComponent;
use Illuminate\Contracts\View\View;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class InventoryItemDependencyEdgesTable extends TableComponent
{
    public int $inventoryItemId;

    private ?InventoryItem $cachedInventoryItem = null;

    public function mount(int $inventoryItemId): void
    {
        $this->inventoryItemId = $inventoryItemId;

        $this->resolveInventoryItem();
    }

    public function table(Table $table): Table
    {
        return $table
            ->queryStringIdentifier('inventoryItemDependencyEdges'.Str::studly((string) $this->inventoryItemId))
            ->defaultSort('relationship_label')
            ->defaultPaginationPageOption(10)
            ->paginated(TablePaginationProfiles::picker())
            ->striped()
            ->deferLoading(! app()->runningUnitTests())
            ->records(function (
                ?string $sortColumn,
                ?string $sortDirection,
                ?string $search,
                array $filters,
                int $page,
                int $recordsPerPage
            ): LengthAwarePaginator {
                $rows = $this->dependencyRows(
                    direction: (string) ($filters['direction']['value'] ?? 'all'),
                    relationshipType: $this->normalizeRelationshipType($filters['relationship_type']['value'] ?? null),
                );

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

                return $this->paginateRows($rows, $page, $recordsPerPage);
            })
            ->filters([
                SelectFilter::make('direction')
                    ->label('Direction')
                    ->default('all')
                    ->options([
                        'all' => 'All',
                        'inbound' => 'Inbound',
                        'outbound' => 'Outbound',
                    ]),
                SelectFilter::make('relationship_type')
                    ->label('Relationship')
                    ->options([
                        'all' => 'All',
                        ...RelationshipType::options(),
                    ])
                    ->default('all')
                    ->searchable(),
            ])
            ->columns([
                TextColumn::make('relationship_label')
                    ->label('Relationship')
                    ->badge()
                    ->sortable(),
                TextColumn::make('target_label')
                    ->label('Target')
                    ->badge()
                    ->url(fn (array $record): ?string => is_string($record['target_url'] ?? null) ? $record['target_url'] : null)
                    ->tooltip(fn (array $record): ?string => is_string($record['target_tooltip'] ?? null) ? $record['target_tooltip'] : null)
                    ->wrap(),
                TextColumn::make('missing_state')
                    ->label('Status')
                    ->badge()
                    ->placeholder('—')
                    ->color(fn (?string $state): string => $state === 'Missing' ? 'danger' : 'gray')
                    ->icon(fn (?string $state): ?string => $state === 'Missing' ? 'heroicon-m-exclamation-triangle' : null)
                    ->description(fn (array $record): ?string => is_string($record['missing_hint'] ?? null) ? $record['missing_hint'] : null)
                    ->wrap(),
            ])
            ->actions([])
            ->bulkActions([])
            ->emptyStateHeading('No dependencies found')
            ->emptyStateDescription('Change direction or relationship filters to review a different dependency scope.');
    }

    public function render(): View
    {
        return view('livewire.inventory-item-dependency-edges-table');
    }

    /**
     * @return Collection<string, array<string, mixed>>
     */
    private function dependencyRows(string $direction, ?string $relationshipType): Collection
    {
        $inventoryItem = $this->resolveInventoryItem();
        $tenant = $this->resolveCurrentTenant();
        $service = app(DependencyQueryService::class);
        $resolver = app(DependencyTargetResolver::class);

        $edges = collect();

        if ($direction === 'inbound' || $direction === 'all') {
            $edges = $edges->merge($service->getInboundEdges($inventoryItem, $relationshipType));
        }

        if ($direction === 'outbound' || $direction === 'all') {
            $edges = $edges->merge($service->getOutboundEdges($inventoryItem, $relationshipType));
        }

        return $resolver->attachRenderedTargets($edges->take(100), $tenant)
            ->map(function (array $edge): array {
                $targetId = $edge['target_id'] ?? null;
                $renderedTarget = is_array($edge['rendered_target'] ?? null) ? $edge['rendered_target'] : [];
                $badgeText = is_string($renderedTarget['badge_text'] ?? null) ? $renderedTarget['badge_text'] : null;
                $linkUrl = is_string($renderedTarget['link_url'] ?? null) ? $renderedTarget['link_url'] : null;
                $lastKnownName = is_string(data_get($edge, 'metadata.last_known_name')) ? data_get($edge, 'metadata.last_known_name') : null;
                $isMissing = ($edge['target_type'] ?? null) === 'missing';

                $missingHint = null;

                if ($isMissing) {
                    $missingHint = 'Missing target';

                    if (filled($lastKnownName)) {
                        $missingHint .= ". Last known: {$lastKnownName}";
                    }

                    $rawRef = data_get($edge, 'metadata.raw_ref');
                    $encodedRef = $rawRef !== null ? json_encode($rawRef) : null;

                    if (is_string($encodedRef) && $encodedRef !== '') {
                        $missingHint .= '. Ref: '.Str::limit($encodedRef, 200);
                    }
                }

                $fallbackLabel = null;

                if (filled($lastKnownName)) {
                    $fallbackLabel = $lastKnownName;
                } elseif (is_string($targetId) && $targetId !== '') {
                    $fallbackLabel = 'ID: '.substr($targetId, 0, 6).'…';
                } else {
                    $fallbackLabel = 'External reference';
                }

                $relationshipType = (string) ($edge['relationship_type'] ?? 'unknown');

                return [
                    'id' => (string) ($edge['id'] ?? Str::uuid()->toString()),
                    'relationship_label' => RelationshipType::options()[$relationshipType] ?? Str::headline($relationshipType),
                    'target_label' => $badgeText ?? $fallbackLabel,
                    'target_url' => $linkUrl,
                    'target_tooltip' => is_string($targetId) ? $targetId : null,
                    'missing_state' => $isMissing ? 'Missing' : null,
                    'missing_hint' => $missingHint,
                ];
            })
            ->mapWithKeys(fn (array $row): array => [$row['id'] => $row]);
    }

    /**
     * @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, ['relationship_label', 'target_label', 'missing_state'], true)
            ? $sortColumn
            : 'relationship_label';
        $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['target_label'] ?? ''),
                    (string) ($right['target_label'] ?? ''),
                );
            }

            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 resolveInventoryItem(): InventoryItem
    {
        if ($this->cachedInventoryItem instanceof InventoryItem) {
            return $this->cachedInventoryItem;
        }

        $inventoryItem = InventoryItem::query()->findOrFail($this->inventoryItemId);
        $tenant = $this->resolveCurrentTenant();

        if (! $this->canViewInventoryItem($inventoryItem, $tenant)) {
            throw new NotFoundHttpException;
        }

        return $this->cachedInventoryItem = $inventoryItem;
    }

    private function resolveCurrentTenant(): ManagedEnvironment
    {
        $tenant = Filament::getTenant();

        if (! $tenant instanceof ManagedEnvironment) {
            $tenant = app(WorkspaceContext::class)->rememberedEnvironment(request());
        }

        if (! $tenant instanceof ManagedEnvironment) {
            throw new NotFoundHttpException;
        }

        return $tenant;
    }

    private function canViewInventoryItem(InventoryItem $inventoryItem, ManagedEnvironment $tenant): bool
    {
        $user = auth()->user();

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

        $capabilityResolver = app(CapabilityResolver::class);

        return (int) $inventoryItem->managed_environment_id === (int) $tenant->getKey()
            && $capabilityResolver->isMember($user, $tenant)
            && $capabilityResolver->can($user, $tenant, Capabilities::TENANT_VIEW);
    }

    private function normalizeRelationshipType(mixed $value): ?string
    {
        if (! is_string($value) || $value === '' || $value === 'all') {
            return null;
        }

        return RelationshipType::tryFrom($value)?->value;
    }
}
