<?php

declare(strict_types=1);

namespace App\Filament\Pages;

use App\Filament\Resources\FindingResource;
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Auth\CapabilityResolver;
use App\Services\Baselines\BaselineCompareService;
use App\Support\Auth\Capabilities;
use App\Support\Baselines\BaselineCaptureMode;
use App\Support\Baselines\BaselineCompareStats;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\OperationUxPresenter;
use App\Support\OpsUx\OpsUxBrowserEvents;
use App\Support\Rbac\UiEnforcement;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use UnitEnum;

class BaselineCompareLanding extends Page
{
    protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-scale';

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

    protected static ?string $navigationLabel = 'Baseline Compare';

    protected static ?int $navigationSort = 10;

    protected static ?string $title = 'Baseline Compare';

    protected string $view = 'filament.pages.baseline-compare-landing';

    public ?string $state = null;

    public ?string $message = null;

    public ?string $reasonCode = null;

    public ?string $reasonMessage = null;

    public ?string $profileName = null;

    public ?int $profileId = null;

    public ?int $snapshotId = null;

    public ?int $duplicateNamePoliciesCount = null;

    public ?int $operationRunId = null;

    public ?int $findingsCount = null;

    /** @var array<string, int>|null */
    public ?array $severityCounts = null;

    public ?string $lastComparedAt = null;

    public ?string $lastComparedIso = null;

    public ?string $failureReason = null;

    public ?string $coverageStatus = null;

    public ?int $uncoveredTypesCount = null;

    /** @var list<string>|null */
    public ?array $uncoveredTypes = null;

    public ?string $fidelity = null;

    public ?int $evidenceGapsCount = null;

    /** @var array<string, int>|null */
    public ?array $evidenceGapsTopReasons = null;

    /** @var array<string, int>|null */
    public ?array $rbacRoleDefinitionSummary = null;

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

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

        $tenant = Tenant::current();

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

        $resolver = app(CapabilityResolver::class);

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

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

    public function refreshStats(): void
    {
        $stats = BaselineCompareStats::forTenant(Tenant::current());

        $this->state = $stats->state;
        $this->message = $stats->message;
        $this->profileName = $stats->profileName;
        $this->profileId = $stats->profileId;
        $this->snapshotId = $stats->snapshotId;
        $this->duplicateNamePoliciesCount = $stats->duplicateNamePoliciesCount;
        $this->operationRunId = $stats->operationRunId;
        $this->findingsCount = $stats->findingsCount;
        $this->severityCounts = $stats->severityCounts !== [] ? $stats->severityCounts : null;
        $this->lastComparedAt = $stats->lastComparedHuman;
        $this->lastComparedIso = $stats->lastComparedIso;
        $this->failureReason = $stats->failureReason;
        $this->reasonCode = $stats->reasonCode;
        $this->reasonMessage = $stats->reasonMessage;

        $this->coverageStatus = $stats->coverageStatus;
        $this->uncoveredTypesCount = $stats->uncoveredTypesCount;
        $this->uncoveredTypes = $stats->uncoveredTypes !== [] ? $stats->uncoveredTypes : null;
        $this->fidelity = $stats->fidelity;

        $this->evidenceGapsCount = $stats->evidenceGapsCount;
        $this->evidenceGapsTopReasons = $stats->evidenceGapsTopReasons !== [] ? $stats->evidenceGapsTopReasons : null;
        $this->rbacRoleDefinitionSummary = $stats->rbacRoleDefinitionSummary !== [] ? $stats->rbacRoleDefinitionSummary : null;
    }

    /**
     * Computed view data exposed to the Blade template.
     *
     * Moves presentational logic out of Blade `@php` blocks so the
     * template only receives ready-to-render values.
     *
     * @return array<string, mixed>
     */
    protected function getViewData(): array
    {
        $hasCoverageWarnings = in_array($this->coverageStatus, ['warning', 'unproven'], true);
        $evidenceGapsCountValue = (int) ($this->evidenceGapsCount ?? 0);
        $hasEvidenceGaps = $evidenceGapsCountValue > 0;
        $hasWarnings = $hasCoverageWarnings || $hasEvidenceGaps;
        $hasRbacRoleDefinitionSummary = is_array($this->rbacRoleDefinitionSummary)
            && array_sum($this->rbacRoleDefinitionSummary) > 0;

        $evidenceGapsSummary = null;
        $evidenceGapsTooltip = null;

        if ($hasEvidenceGaps && is_array($this->evidenceGapsTopReasons) && $this->evidenceGapsTopReasons !== []) {
            $parts = [];

            foreach (array_slice($this->evidenceGapsTopReasons, 0, 5, true) as $reason => $count) {
                if (! is_string($reason) || $reason === '' || ! is_numeric($count)) {
                    continue;
                }

                $parts[] = $reason.' ('.((int) $count).')';
            }

            if ($parts !== []) {
                $evidenceGapsSummary = implode(', ', $parts);
                $evidenceGapsTooltip = __('baseline-compare.evidence_gaps_tooltip', ['summary' => $evidenceGapsSummary]);
            }
        }

        // Derive the colour class for the findings-count stat card.
        // Only show danger-red when high-severity findings exist;
        // use warning-orange for low/medium-only, and success-green for zero.
        $findingsColorClass = $this->resolveFindingsColorClass($hasWarnings);

        // "Why no findings" explanation when count is zero.
        $whyNoFindingsMessage = filled($this->reasonMessage) ? (string) $this->reasonMessage : null;
        $whyNoFindingsFallback = ! $hasWarnings
            ? __('baseline-compare.no_findings_all_clear')
            : ($hasCoverageWarnings
                ? __('baseline-compare.no_findings_coverage_warnings')
                : ($hasEvidenceGaps
                    ? __('baseline-compare.no_findings_evidence_gaps')
                    : __('baseline-compare.no_findings_default')));
        $whyNoFindingsColor = $hasWarnings
            ? 'text-warning-600 dark:text-warning-400'
            : 'text-success-600 dark:text-success-400';

        if ($this->reasonCode === 'no_subjects_in_scope') {
            $whyNoFindingsColor = 'text-gray-600 dark:text-gray-400';
        }

        return [
            'hasCoverageWarnings' => $hasCoverageWarnings,
            'evidenceGapsCountValue' => $evidenceGapsCountValue,
            'hasEvidenceGaps' => $hasEvidenceGaps,
            'hasWarnings' => $hasWarnings,
            'hasRbacRoleDefinitionSummary' => $hasRbacRoleDefinitionSummary,
            'evidenceGapsSummary' => $evidenceGapsSummary,
            'evidenceGapsTooltip' => $evidenceGapsTooltip,
            'findingsColorClass' => $findingsColorClass,
            'whyNoFindingsMessage' => $whyNoFindingsMessage,
            'whyNoFindingsFallback' => $whyNoFindingsFallback,
            'whyNoFindingsColor' => $whyNoFindingsColor,
        ];
    }

    /**
     * Resolve the Tailwind colour class for the Total Findings stat.
     *
     * - Red (danger) only when high-severity findings exist
     * - Orange (warning) for medium/low-only findings or when warnings present
     * - Green (success) when fully clear
     */
    private function resolveFindingsColorClass(bool $hasWarnings): string
    {
        $count = (int) ($this->findingsCount ?? 0);

        if ($count === 0) {
            return $hasWarnings
                ? 'text-warning-600 dark:text-warning-400'
                : 'text-success-600 dark:text-success-400';
        }

        $hasHigh = ($this->severityCounts['high'] ?? 0) > 0;

        return $hasHigh
            ? 'text-danger-600 dark:text-danger-400'
            : 'text-warning-600 dark:text-warning-400';
    }

    public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
    {
        return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly)
            ->satisfy(ActionSurfaceSlot::ListHeader, 'Header action: Compare Now (confirmation modal, capability-gated).')
            ->exempt(ActionSurfaceSlot::InspectAffordance, 'This is a tenant-scoped landing page, not a record inspect surface.')
            ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'This page does not render table rows with secondary actions.')
            ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'This page has no bulk actions.')
            ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Page renders explicit empty states for missing tenant, missing assignment, and missing snapshot, with guidance messaging.')
            ->exempt(ActionSurfaceSlot::DetailHeader, 'This page does not have a record detail header; it uses a page header action instead.');
    }

    /**
     * @return array<Action>
     */
    protected function getHeaderActions(): array
    {
        return [
            $this->compareNowAction(),
        ];
    }

    private function compareNowAction(): Action
    {
        $isFullContent = false;

        if (is_int($this->profileId) && $this->profileId > 0) {
            $profile = \App\Models\BaselineProfile::query()->find($this->profileId);
            $mode = $profile?->capture_mode instanceof BaselineCaptureMode
                ? $profile->capture_mode
                : (is_string($profile?->capture_mode) ? BaselineCaptureMode::tryFrom($profile->capture_mode) : null);

            $isFullContent = $mode === BaselineCaptureMode::FullContent;
        }

        $label = $isFullContent ? 'Compare now (full content)' : 'Compare now';
        $modalDescription = $isFullContent
            ? 'This will refresh content evidence on demand (redacted) before comparing the current tenant inventory against the assigned baseline snapshot.'
            : 'This will compare the current tenant inventory against the assigned baseline snapshot and generate drift findings.';

        $action = Action::make('compareNow')
            ->label($label)
            ->icon('heroicon-o-play')
            ->requiresConfirmation()
            ->modalHeading($label)
            ->modalDescription($modalDescription)
            ->disabled(fn (): bool => ! in_array($this->state, ['idle', 'ready', 'failed'], true))
            ->action(function (): void {
                $user = auth()->user();

                if (! $user instanceof User) {
                    Notification::make()->title('Not authenticated')->danger()->send();

                    return;
                }

                $tenant = Tenant::current();

                if (! $tenant instanceof Tenant) {
                    Notification::make()->title('No tenant context')->danger()->send();

                    return;
                }

                $service = app(BaselineCompareService::class);
                $result = $service->startCompare($tenant, $user);

                if (! ($result['ok'] ?? false)) {
                    Notification::make()
                        ->title('Cannot start comparison')
                        ->body('Reason: '.($result['reason_code'] ?? 'unknown'))
                        ->danger()
                        ->send();

                    return;
                }

                $run = $result['run'] ?? null;

                if ($run instanceof OperationRun) {
                    $this->operationRunId = (int) $run->getKey();
                }

                $this->state = 'comparing';

                OpsUxBrowserEvents::dispatchRunEnqueued($this);

                OperationUxPresenter::queuedToast($run instanceof OperationRun ? (string) $run->type : 'baseline_compare')
                    ->actions($run instanceof OperationRun ? [
                        Action::make('view_run')
                            ->label('View run')
                            ->url(OperationRunLinks::view($run, $tenant)),
                    ] : [])
                    ->send();
            });

        return UiEnforcement::forAction($action)
            ->requireCapability(Capabilities::TENANT_SYNC)
            ->preserveDisabled()
            ->apply();
    }

    public function getFindingsUrl(): ?string
    {
        $tenant = Tenant::current();

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

        return FindingResource::getUrl('index', tenant: $tenant);
    }

    public function getRunUrl(): ?string
    {
        if ($this->operationRunId === null) {
            return null;
        }

        $tenant = Tenant::current();

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

        return OperationRunLinks::view($this->operationRunId, $tenant);
    }
}
