<?php

declare(strict_types=1);

namespace App\Support\PortfolioTriage;

use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\RestoreSafety\RestoreResultAttention;
use App\Support\Tenants\TenantRecoveryTriagePresentation;
use JsonException;

final class PortfolioArrivalContextToken
{
    public const string QUERY_PARAMETER = 'arrival';

    public const string SOURCE_WORKSPACE_OVERVIEW = 'workspace_overview';

    public const string SOURCE_TENANT_REGISTRY = 'tenant_registry';

    public const string FAMILY_BACKUP_HEALTH = 'backup_health';

    public const string FAMILY_RECOVERY_EVIDENCE = 'recovery_evidence';

    private const int VERSION = 1;

    private const array SOURCE_ALLOWLIST = [
        self::SOURCE_WORKSPACE_OVERVIEW => true,
        self::SOURCE_TENANT_REGISTRY => true,
    ];

    private const array BACKUP_STATE_ALLOWLIST = [
        TenantBackupHealthAssessment::POSTURE_ABSENT => true,
        TenantBackupHealthAssessment::POSTURE_STALE => true,
        TenantBackupHealthAssessment::POSTURE_DEGRADED => true,
    ];

    private const array RECOVERY_STATE_ALLOWLIST = [
        TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_UNVALIDATED => true,
        TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED => true,
    ];

    private const array BACKUP_REASON_ALLOWLIST = [
        TenantBackupHealthAssessment::REASON_NO_BACKUP_BASIS => true,
        TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE => true,
        TenantBackupHealthAssessment::REASON_LATEST_BACKUP_DEGRADED => true,
    ];

    private const array RECOVERY_REASON_ALLOWLIST = [
        'no_history' => true,
        RestoreResultAttention::STATE_FAILED => true,
        RestoreResultAttention::STATE_PARTIAL => true,
        RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP => true,
    ];

    private const array RETURN_FILTER_ALLOWLIST = [
        'backup_posture' => true,
        'recovery_evidence' => true,
        'review_state' => true,
        'triage_sort' => true,
    ];

    /**
     * @param  array<string, mixed>  $state
     */
    public static function encode(array $state): string
    {
        $payload = ['v' => self::VERSION] + $state;

        $json = json_encode($payload, JSON_THROW_ON_ERROR);

        return self::base64UrlEncode($json);
    }

    /**
     * @return array{
     *     sourceSurface: string,
     *     tenantRouteKey: string|null,
     *     workspaceId: int|null,
     *     concernFamily: string,
     *     concernState: string,
     *     concernReason: string|null,
     *     returnFilters: array<string, mixed>|null
     * }|null
     */
    public static function decode(?string $token): ?array
    {
        if (! is_string($token) || trim($token) === '') {
            return null;
        }

        $json = self::base64UrlDecode(trim($token));

        if ($json === null) {
            return null;
        }

        try {
            $decoded = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
        } catch (JsonException) {
            return null;
        }

        if (! is_array($decoded)) {
            return null;
        }

        $version = $decoded['v'] ?? null;
        $version = is_int($version) ? $version : (is_numeric($version) ? (int) $version : null);

        if ($version !== self::VERSION) {
            return null;
        }

        $sourceSurface = $decoded['sourceSurface'] ?? null;

        if (! is_string($sourceSurface) || ! isset(self::SOURCE_ALLOWLIST[$sourceSurface])) {
            return null;
        }

        $concernFamily = $decoded['concernFamily'] ?? null;

        if (! is_string($concernFamily) || ! in_array($concernFamily, [
            self::FAMILY_BACKUP_HEALTH,
            self::FAMILY_RECOVERY_EVIDENCE,
        ], true)) {
            return null;
        }

        $concernState = $decoded['concernState'] ?? null;

        if (! is_string($concernState) || ! self::isAllowedState($concernFamily, $concernState)) {
            return null;
        }

        $concernReason = $decoded['concernReason'] ?? null;

        if (! is_string($concernReason) && $concernReason !== null) {
            return null;
        }

        if (! self::isAllowedReason($concernFamily, $concernReason)) {
            return null;
        }

        $tenantRouteKey = $decoded['tenantRouteKey'] ?? null;

        if (! is_string($tenantRouteKey) && $tenantRouteKey !== null) {
            return null;
        }

        $tenantRouteKey = is_string($tenantRouteKey) && $tenantRouteKey !== ''
            ? $tenantRouteKey
            : null;

        $workspaceId = $decoded['workspaceId'] ?? null;
        $workspaceId = is_int($workspaceId) ? $workspaceId : (is_numeric($workspaceId) ? (int) $workspaceId : null);

        $returnFilters = self::sanitizeReturnFilters($decoded['returnFilters'] ?? null);

        return [
            'sourceSurface' => $sourceSurface,
            'tenantRouteKey' => $tenantRouteKey,
            'workspaceId' => $workspaceId,
            'concernFamily' => $concernFamily,
            'concernState' => $concernState,
            'concernReason' => $concernReason,
            'returnFilters' => $returnFilters,
        ];
    }

    private static function isAllowedState(string $family, string $state): bool
    {
        return match ($family) {
            self::FAMILY_BACKUP_HEALTH => isset(self::BACKUP_STATE_ALLOWLIST[$state]),
            self::FAMILY_RECOVERY_EVIDENCE => isset(self::RECOVERY_STATE_ALLOWLIST[$state]),
            default => false,
        };
    }

    private static function isAllowedReason(string $family, ?string $reason): bool
    {
        if ($reason === null) {
            return true;
        }

        return match ($family) {
            self::FAMILY_BACKUP_HEALTH => isset(self::BACKUP_REASON_ALLOWLIST[$reason]),
            self::FAMILY_RECOVERY_EVIDENCE => isset(self::RECOVERY_REASON_ALLOWLIST[$reason]),
            default => false,
        };
    }

    /**
     * @return array<string, mixed>|null
     */
    private static function sanitizeReturnFilters(mixed $filters): ?array
    {
        if (! is_array($filters)) {
            return null;
        }

        $sanitized = [];

        foreach ($filters as $key => $value) {
            if (! is_string($key) || ! isset(self::RETURN_FILTER_ALLOWLIST[$key])) {
                continue;
            }

            if (is_scalar($value) || $value === null) {
                $sanitized[$key] = $value;

                continue;
            }

            if (! is_array($value)) {
                continue;
            }

            $sanitized[$key] = array_values(array_filter(
                $value,
                static fn (mixed $item): bool => is_scalar($item) && $item !== '',
            ));
        }

        return $sanitized !== [] ? $sanitized : null;
    }

    private static function base64UrlEncode(string $value): string
    {
        return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
    }

    private static function base64UrlDecode(string $value): ?string
    {
        $padded = strtr($value, '-_', '+/');
        $padding = strlen($padded) % 4;

        if ($padding !== 0) {
            $padded .= str_repeat('=', 4 - $padding);
        }

        $decoded = base64_decode($padded, true);

        return is_string($decoded) ? $decoded : null;
    }
}
