<?php

declare(strict_types=1);

namespace App\Support\PortfolioTriage;

use App\Filament\Resources\BackupSetResource;
use App\Filament\Resources\RestoreRunResource;
use App\Filament\Resources\ManagedEnvironmentResource;
use App\Models\RestoreRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Auth\CapabilityResolver;
use App\Support\Auth\Capabilities;
use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\BackupHealth\TenantBackupHealthResolver;
use App\Support\ManagedEnvironmentLinks;
use App\Support\Rbac\UiTooltips;
use App\Support\RestoreSafety\RestoreResultAttention;
use App\Support\RestoreSafety\RestoreSafetyResolver;
use App\Support\Tenants\TenantRecoveryTriagePresentation;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Http\Request;

final readonly class PortfolioArrivalContextResolver
{
    private const string REQUEST_CACHE_KEY = 'portfolio_triage.arrival_context';

    private const string REQUEST_CACHE_HIT_KEY = 'portfolio_triage.arrival_context.hit';

    public function __construct(
        private CapabilityResolver $capabilityResolver,
        private TenantBackupHealthResolver $tenantBackupHealthResolver,
        private RestoreSafetyResolver $restoreSafetyResolver,
        private WorkspaceContext $workspaceContext,
    ) {}

    public function resolve(Request $request, ManagedEnvironment $tenant): ?PortfolioArrivalContext
    {
        if ($request->attributes->has(self::REQUEST_CACHE_HIT_KEY)) {
            $cached = $request->attributes->get(self::REQUEST_CACHE_KEY);

            return $cached instanceof PortfolioArrivalContext ? $cached : null;
        }

        $request->attributes->set(self::REQUEST_CACHE_HIT_KEY, true);

        $state = PortfolioArrivalContextToken::decode(
            $request->query(PortfolioArrivalContextToken::QUERY_PARAMETER),
        );

        if ($state === null || ! $this->matchesRequestScope($tenant, $request, $state)) {
            $request->attributes->set(self::REQUEST_CACHE_KEY, null);

            return null;
        }

        $context = $this->buildContext($tenant, $state);

        $request->attributes->set(self::REQUEST_CACHE_KEY, $context);

        return $context;
    }

    /**
     * @param  array{
     *     sourceSurface: string,
     *     tenantRouteKey: string|null,
     *     workspaceId: int|null,
     *     concernFamily: string,
     *     concernState: string,
     *     concernReason: string|null,
     *     returnFilters: array<string, mixed>|null
     * }|null  $state
     */
    public function resolveState(ManagedEnvironment $tenant, ?array $state): ?PortfolioArrivalContext
    {
        return $this->resolveStateWithTruth($tenant, $state);
    }

    /**
     * @param  array{
     *     sourceSurface: string,
     *     tenantRouteKey: string|null,
     *     workspaceId: int|null,
     *     concernFamily: string,
     *     concernState: string,
     *     concernReason: string|null,
     *     returnFilters: array<string, mixed>|null
     * }|null  $state
     * @param  array<string, mixed>|null  $recoveryEvidence
     */
    public function resolveStateWithTruth(
        ManagedEnvironment $tenant,
        ?array $state,
        ?TenantBackupHealthAssessment $backupHealth = null,
        ?array $recoveryEvidence = null,
    ): ?PortfolioArrivalContext
    {
        if ($state === null || ! $this->matchesTenantScope($tenant, $state)) {
            return null;
        }

        return $this->buildContext($tenant, $state, $backupHealth, $recoveryEvidence);
    }

    /**
     * @param  array{
     *     sourceSurface: string,
     *     tenantRouteKey: string|null,
     *     workspaceId: int|null,
     *     concernFamily: string,
     *     concernState: string,
     *     concernReason: string|null,
     *     returnFilters: array<string, mixed>|null
     * }  $state
     */
    private function matchesRequestScope(ManagedEnvironment $tenant, Request $request, array $state): bool
    {
        if (! $this->matchesTenantScope($tenant, $state)) {
            return false;
        }

        $workspaceId = $state['workspaceId'];

        return $workspaceId === null
            || $this->workspaceContext->currentWorkspaceId($request) === $workspaceId;
    }

    /**
     * @param  array{
     *     sourceSurface: string,
     *     tenantRouteKey: string|null,
     *     workspaceId: int|null,
     *     concernFamily: string,
     *     concernState: string,
     *     concernReason: string|null,
     *     returnFilters: array<string, mixed>|null
     * }  $state
     */
    private function matchesTenantScope(ManagedEnvironment $tenant, array $state): bool
    {
        $tenantRouteKey = $state['tenantRouteKey'];

        if (! is_string($tenantRouteKey) || $tenantRouteKey === '') {
            return false;
        }

        $allowedRouteKeys = array_filter([
            filled($tenant->external_id) ? (string) $tenant->external_id : null,
            (string) $tenant->getKey(),
        ]);

        if (! in_array($tenantRouteKey, $allowedRouteKeys, true)) {
            return false;
        }

        $workspaceId = $state['workspaceId'];

        return $workspaceId === null
            || (int) $tenant->workspace_id === $workspaceId;
    }

    /**
     * @param  array{
     *     sourceSurface: string,
     *     tenantRouteKey: string|null,
     *     workspaceId: int|null,
     *     concernFamily: string,
     *     concernState: string,
     *     concernReason: string|null,
     *     returnFilters: array<string, mixed>|null
     * }  $state
     */
    /**
     * @param  array<string, mixed>|null  $recoveryEvidence
     */
    private function buildContext(
        ManagedEnvironment $tenant,
        array $state,
        ?TenantBackupHealthAssessment $backupHealth = null,
        ?array $recoveryEvidence = null,
    ): PortfolioArrivalContext
    {
        $backupHealth ??= $this->tenantBackupHealthResolver->assess($tenant);
        $recoveryEvidence ??= $this->restoreSafetyResolver->dashboardRecoveryEvidence($tenant);

        return new PortfolioArrivalContext(
            sourceSurface: $state['sourceSurface'],
            concernFamily: $state['concernFamily'],
            concernState: $state['concernState'],
            concernReason: $state['concernReason'],
            arrivalSummary: $this->arrivalSummary(
                $state['sourceSurface'],
                $state['concernFamily'],
                $state['concernState'],
                $state['concernReason'],
            ),
            claimBoundary: $this->claimBoundary($state['concernFamily'], $backupHealth, $recoveryEvidence),
            nextStep: $this->nextStepTarget(
                tenant: $tenant,
                concernFamily: $state['concernFamily'],
                concernState: $state['concernState'],
                concernReason: $state['concernReason'],
                recoveryEvidence: $recoveryEvidence,
            ),
            returnTarget: $this->returnTarget(
                sourceSurface: $state['sourceSurface'],
                returnFilters: $state['returnFilters'],
            ),
            currentTruthDelta: $this->currentTruthDelta(
                concernFamily: $state['concernFamily'],
                concernState: $state['concernState'],
                backupHealth: $backupHealth,
                recoveryEvidence: $recoveryEvidence,
            ),
        );
    }

    private function arrivalSummary(
        string $sourceSurface,
        string $concernFamily,
        string $concernState,
        ?string $concernReason,
    ): string {
        $source = $sourceSurface === PortfolioArrivalContextToken::SOURCE_WORKSPACE_OVERVIEW
            ? 'workspace overview triage'
            : 'tenant registry triage';

        return match ($concernFamily) {
            PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH => match ($concernState) {
                TenantBackupHealthAssessment::POSTURE_ABSENT => "Opened from {$source} because no usable backup basis was visible.",
                TenantBackupHealthAssessment::POSTURE_STALE => "Opened from {$source} because the latest backup was stale.",
                TenantBackupHealthAssessment::POSTURE_DEGRADED => "Opened from {$source} because the latest backup was degraded.",
                default => "Opened from {$source} for backup follow-up.",
            },
            PortfolioArrivalContextToken::FAMILY_RECOVERY_EVIDENCE => match ($concernReason ?? $concernState) {
                'no_history' => "Opened from {$source} because recovery evidence was unvalidated.",
                RestoreResultAttention::STATE_FAILED => "Opened from {$source} because a recent restore failed.",
                RestoreResultAttention::STATE_PARTIAL => "Opened from {$source} because a recent restore completed partially.",
                RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP => "Opened from {$source} because restore follow-up is still required.",
                default => "Opened from {$source} because recovery evidence still needs follow-up.",
            },
            default => "Opened from {$source}.",
        };
    }

    /**
     * @param  array<string, mixed>  $recoveryEvidence
     */
    private function claimBoundary(
        string $concernFamily,
        TenantBackupHealthAssessment $backupHealth,
        array $recoveryEvidence,
    ): ?string {
        if ($concernFamily === PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH) {
            return $backupHealth->positiveClaimBoundary;
        }

        $claimBoundary = $recoveryEvidence['claim_boundary'] ?? null;

        return is_string($claimBoundary) && $claimBoundary !== ''
            ? $claimBoundary
            : null;
    }

    /**
     * @param  array<string, mixed>  $recoveryEvidence
     * @return array{
     *     kind: string,
     *     label: string,
     *     url: string|null,
     *     disabled: bool,
     *     helperText: string|null
     * }
     */
    private function nextStepTarget(
        ManagedEnvironment $tenant,
        string $concernFamily,
        string $concernState,
        ?string $concernReason,
        array $recoveryEvidence,
    ): array {
        return match ($concernFamily) {
            PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH => $this->backupNextStepTarget(
                tenant: $tenant,
                concernState: $concernState,
                concernReason: $concernReason,
            ),
            PortfolioArrivalContextToken::FAMILY_RECOVERY_EVIDENCE => $this->recoveryNextStepTarget(
                tenant: $tenant,
                concernState: $concernState,
                concernReason: $concernReason,
                recoveryEvidence: $recoveryEvidence,
            ),
            default => $this->disabledTarget(kind: 'tenant_dashboard', label: 'Open environment'),
        };
    }

    /**
     * @return array{
     *     kind: string,
     *     label: string,
     *     url: string|null,
     *     disabled: bool,
     *     helperText: string|null
     * }
     */
    private function backupNextStepTarget(ManagedEnvironment $tenant, string $concernState, ?string $concernReason): array
    {
        $label = 'Open backup sets';

        if (! $this->canOpenTenantFollowUp($tenant)) {
            return $this->disabledTarget(kind: 'backup_sets', label: $label);
        }

        $reason = is_string($concernReason) && $concernReason !== ''
            ? $concernReason
            : match ($concernState) {
                TenantBackupHealthAssessment::POSTURE_ABSENT => TenantBackupHealthAssessment::REASON_NO_BACKUP_BASIS,
                TenantBackupHealthAssessment::POSTURE_STALE => TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE,
                default => TenantBackupHealthAssessment::REASON_LATEST_BACKUP_DEGRADED,
            };

        return [
            'kind' => 'backup_sets',
            'label' => $label,
            'url' => BackupSetResource::getUrl('index', [
                'backup_health_reason' => $reason,
            ], tenant: $tenant),
            'disabled' => false,
            'helperText' => null,
        ];
    }

    /**
     * @param  array<string, mixed>  $recoveryEvidence
     * @return array{
     *     kind: string,
     *     label: string,
     *     url: string|null,
     *     disabled: bool,
     *     helperText: string|null
     * }
     */
    private function recoveryNextStepTarget(
        ManagedEnvironment $tenant,
        string $concernState,
        ?string $concernReason,
        array $recoveryEvidence,
    ): array {
        $resolvedReason = is_string($concernReason) && $concernReason !== ''
            ? $concernReason
            : null;
        $currentReason = is_string($recoveryEvidence['reason'] ?? null)
            ? $recoveryEvidence['reason']
            : null;

        if (in_array($currentReason, [
            'no_history',
            'no_recent_issues_visible',
            RestoreResultAttention::STATE_FAILED,
            RestoreResultAttention::STATE_PARTIAL,
            RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP,
        ], true)) {
            $resolvedReason = $currentReason;
        }

        if (! is_string($resolvedReason) || $resolvedReason === '') {
            $resolvedReason = $concernState === TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_UNVALIDATED
                ? 'no_history'
                : RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP;
        }

        if (! $this->canOpenTenantFollowUp($tenant)) {
            return $this->disabledTarget(
                kind: 'restore_runs',
                label: in_array($resolvedReason, [
                    RestoreResultAttention::STATE_FAILED,
                    RestoreResultAttention::STATE_PARTIAL,
                    RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP,
                ], true)
                    ? 'Open restore run'
                    : 'Open restore history',
            );
        }

        $latestRunId = is_numeric($recoveryEvidence['latest_relevant_restore_run_id'] ?? null)
            ? (int) $recoveryEvidence['latest_relevant_restore_run_id']
            : null;

        if (in_array($resolvedReason, [
            RestoreResultAttention::STATE_FAILED,
            RestoreResultAttention::STATE_PARTIAL,
            RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP,
        ], true) && $latestRunId !== null) {
            if (RestoreRun::query()
                ->whereKey($latestRunId)
                ->where('managed_environment_id', (int) $tenant->getKey())
                ->exists()) {
                return [
                    'kind' => 'restore_run_detail',
                    'label' => 'Open restore run',
                    'url' => RestoreRunResource::getUrl('view', [
                        'record' => $latestRunId,
                        'recovery_posture_reason' => $resolvedReason,
                    ], tenant: $tenant),
                    'disabled' => false,
                    'helperText' => null,
                ];
            }

            return [
                'kind' => 'restore_runs',
                'label' => 'Open restore history',
                'url' => RestoreRunResource::getUrl('index', [
                    'recovery_posture_reason' => $resolvedReason,
                ], tenant: $tenant),
                'disabled' => false,
                'helperText' => 'The latest restore detail is no longer available.',
            ];
        }

        return [
            'kind' => 'restore_runs',
            'label' => 'Open restore history',
            'url' => RestoreRunResource::getUrl('index', [
                'recovery_posture_reason' => $resolvedReason,
            ], tenant: $tenant),
            'disabled' => false,
            'helperText' => null,
        ];
    }

    /**
     * @param  array<string, mixed>|null  $returnFilters
     * @return array{
     *     kind: string,
     *     label: string,
     *     url: string,
     *     filters: array<string, mixed>|null
     * }|null
     */
    private function returnTarget(string $sourceSurface, ?array $returnFilters): ?array
    {
        if ($sourceSurface === PortfolioArrivalContextToken::SOURCE_WORKSPACE_OVERVIEW) {
            return [
                'kind' => 'workspace_overview',
                'label' => 'Return to workspace overview',
                'url' => route('admin.home'),
                'filters' => null,
            ];
        }

        $filters = $this->sanitizeRegistryReturnFilters($returnFilters ?? []);

        if ($filters === []) {
            return null;
        }

        return [
            'kind' => 'tenant_registry',
            'label' => 'Return to tenant triage',
            'url' => ManagedEnvironmentLinks::indexUrl(query: $filters),
            'filters' => $filters,
        ];
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return array<string, mixed>
     */
    private function sanitizeRegistryReturnFilters(array $filters): array
    {
        $sanitized = [];

        $backupPostures = ManagedEnvironmentResource::sanitizeBackupPostures($filters['backup_posture'] ?? []);

        if ($backupPostures !== []) {
            $sanitized['backup_posture'] = $backupPostures;
        }

        $recoveryEvidence = ManagedEnvironmentResource::sanitizeRecoveryEvidenceStates($filters['recovery_evidence'] ?? []);

        if ($recoveryEvidence !== []) {
            $sanitized['recovery_evidence'] = $recoveryEvidence;
        }

        $triageSort = ManagedEnvironmentResource::sanitizeTriageSort($filters['triage_sort'] ?? null);

        if ($triageSort !== null) {
            $sanitized['triage_sort'] = $triageSort;
        }

        return $sanitized;
    }

    /**
     * @param  array<string, mixed>  $recoveryEvidence
     */
    private function currentTruthDelta(
        string $concernFamily,
        string $concernState,
        TenantBackupHealthAssessment $backupHealth,
        array $recoveryEvidence,
    ): ?string {
        $parts = [];

        if ($concernFamily === PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH) {
            if ($backupHealth->posture !== $concernState) {
                $parts[] = sprintf(
                    'Current backup posture now looks %s.',
                    $this->labelForState($backupHealth->posture),
                );
            }

            if ($this->hasRecoveryAttention($recoveryEvidence)) {
                $parts[] = 'Recovery evidence also still needs follow-up.';
            }
        } else {
            $currentRecoveryState = is_string($recoveryEvidence['overview_state'] ?? null)
                ? $recoveryEvidence['overview_state']
                : null;

            if ($currentRecoveryState !== null && $currentRecoveryState !== $concernState) {
                $parts[] = sprintf(
                    'Current recovery evidence now looks %s.',
                    $this->labelForState($currentRecoveryState),
                );
            }

            if ($this->hasBackupAttention($backupHealth)) {
                $parts[] = 'Backup posture also still needs follow-up.';
            }
        }

        return $parts !== [] ? implode(' ', $parts) : null;
    }

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

        return $user instanceof User
            && $this->capabilityResolver->can($user, $tenant, Capabilities::TENANT_VIEW);
    }

    /**
     * @return array{
     *     kind: string,
     *     label: string,
     *     url: null,
     *     disabled: true,
     *     helperText: string
     * }
     */
    private function disabledTarget(string $kind, string $label): array
    {
        return [
            'kind' => $kind,
            'label' => $label,
            'url' => null,
            'disabled' => true,
            'helperText' => UiTooltips::INSUFFICIENT_PERMISSION,
        ];
    }

    private function hasBackupAttention(TenantBackupHealthAssessment $backupHealth): bool
    {
        return in_array($backupHealth->posture, [
            TenantBackupHealthAssessment::POSTURE_ABSENT,
            TenantBackupHealthAssessment::POSTURE_STALE,
            TenantBackupHealthAssessment::POSTURE_DEGRADED,
        ], true);
    }

    /**
     * @param  array<string, mixed>  $recoveryEvidence
     */
    private function hasRecoveryAttention(array $recoveryEvidence): bool
    {
        return in_array($recoveryEvidence['overview_state'] ?? null, [
            TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_UNVALIDATED,
            TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED,
        ], true);
    }

    private function labelForState(?string $state): string
    {
        return match ($state) {
            TenantBackupHealthAssessment::POSTURE_ABSENT => 'Absent',
            TenantBackupHealthAssessment::POSTURE_STALE => 'Stale',
            TenantBackupHealthAssessment::POSTURE_DEGRADED => 'Degraded',
            TenantBackupHealthAssessment::POSTURE_HEALTHY => 'Healthy',
            TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_UNVALIDATED => 'Unvalidated',
            TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED => 'Weakened',
            TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_NO_RECENT_ISSUES_VISIBLE => 'No recent issues visible',
            default => 'Updated',
        };
    }
}
