<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

namespace App\Services\SupportCenter;

/**
 * Support Center Open Host 站点绑定状态 → 规范化内部码与面向业务的中文展示。
 *
 * 优先消费 supportCenter 真实字段：is_bound、application_status、task_status、next_action；
 * 兼容历史字段 binding_status / status / site_binding_status / validation_status。
 */
final class HostSiteOnboardingStatusPresenter
{
    public const CANONICAL_BINDING_ACTIVE = 'binding_active';

    public const CANONICAL_BOUND = 'bound';

    public const CANONICAL_PENDING_MANUAL_REVIEW = 'pending_manual_review';

    public const CANONICAL_CALLBACK_FAILED = 'callback_failed';

    public const CANONICAL_EXPIRED = 'expired';

    public const CANONICAL_AWAIT_ACCESS_CONFIRM = 'await_access_confirm';

    public const CANONICAL_NO_ACTIVE_SITE_BINDING = 'no_active_site_binding';

    public const CANONICAL_ALREADY_BOUND = 'already_bound';

    public const CANONICAL_DOMAIN_MISMATCH = 'domain_mismatch';

    public const CANONICAL_INSTANCE_MISMATCH = 'instance_mismatch';

    /**
     * @param  array<string, mixed>  $data  Open Host data 或已缓存的 data 子树
     * @return array{
     *     binding_status: ?string,
     *     binding_status_label: string,
     *     is_bound: bool,
     *     next_action: ?string,
     *     next_action_hint: ?string,
     * }
     */
    public function normalizeFromOpenHostData(array $data): array
    {
        $nextAction = $this->extractNextAction($data);
        $nextActionHint = $this->extractNextActionHint($data);
        $canonical = $this->resolveCanonicalStatus($data, $nextAction);
        $isBound = $this->resolveIsBoundFromData($data, $canonical, $nextAction);

        $label = $this->resolveDisplayLabel($canonical, $nextActionHint);

        return [
            'binding_status' => $canonical,
            'binding_status_label' => $label,
            'is_bound' => $isBound,
            'next_action' => $nextAction,
            'next_action_hint' => $nextActionHint,
        ];
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function extractBindingStatus(array $data): ?string
    {
        return $this->resolveCanonicalStatus($data, $this->extractNextAction($data));
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function resolveIsBoundFromData(array $data, ?string $canonical = null, ?string $nextAction = null): bool
    {
        if ($this->booleanFromMixed($data['is_bound'] ?? null) === true) {
            return true;
        }

        $nextAction ??= $this->extractNextAction($data);
        $canonical ??= $this->resolveCanonicalStatus($data, $nextAction);

        if ($nextAction === self::CANONICAL_BINDING_ACTIVE) {
            return true;
        }

        if ($nextAction === self::CANONICAL_NO_ACTIVE_SITE_BINDING) {
            return false;
        }

        $applicationStatus = $this->normalizedString($data['application_status'] ?? null);

        if ($applicationStatus === self::CANONICAL_BINDING_ACTIVE) {
            return true;
        }

        if ($canonical !== null && $this->isBound($canonical)) {
            return true;
        }

        return false;
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function extractSiteNo(array $data): ?string
    {
        foreach (['official_site_no', 'site_no'] as $key) {
            $value = $data[$key] ?? null;

            if (is_string($value) && trim($value) !== '') {
                return trim($value);
            }
        }

        $siteSummary = $data['site_summary'] ?? null;

        if (is_array($siteSummary)) {
            $nested = $siteSummary['site_no'] ?? null;

            if (is_string($nested) && trim($nested) !== '') {
                return trim($nested);
            }
        }

        return null;
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function extractTaskNo(array $data): ?string
    {
        $value = $data['task_no'] ?? null;

        if (is_string($value) && trim($value) !== '') {
            return trim($value);
        }

        return null;
    }

    public function label(?string $status): string
    {
        $normalized = strtolower(trim((string) $status));

        if ($normalized === '') {
            return (string) __('dberp.site_onboarding.status_labels.no_active_site_binding');
        }

        $translationKey = 'dberp.site_onboarding.status_labels.'.$normalized;
        $translated = __($translationKey);

        if ($translated !== $translationKey) {
            return (string) $translated;
        }

        return match (true) {
            in_array($normalized, ['bound', 'active', 'passed', self::CANONICAL_BINDING_ACTIVE], true) => (string) __('dberp.site_onboarding.status_labels.bound'),
            in_array($normalized, ['validating', 'pending', 'executing', 'await_fixed_file_or_callback', 'await_local_validation', 'await_local_validation_already', 'validation_passed_await_platform'], true) => (string) __('dberp.site_onboarding.status_labels.pending'),
            in_array($normalized, ['failed', 'rejected', 'validation_failed', 'validation_failed_review_or_reapply', 'auto_failed'], true) => (string) __('dberp.site_onboarding.status_labels.failed'),
            $normalized === self::CANONICAL_EXPIRED => (string) __('dberp.site_onboarding.status_labels.expired'),
            $normalized === self::CANONICAL_PENDING_MANUAL_REVIEW => (string) __('dberp.site_onboarding.status_labels.pending_manual_review'),
            $normalized === self::CANONICAL_CALLBACK_FAILED => (string) __('dberp.site_onboarding.status_labels.callback_failed'),
            $normalized === self::CANONICAL_AWAIT_ACCESS_CONFIRM => (string) __('dberp.site_onboarding.status_labels.await_access_confirm'),
            $normalized === self::CANONICAL_ALREADY_BOUND => (string) __('dberp.site_onboarding.status_labels.already_bound'),
            $normalized === self::CANONICAL_DOMAIN_MISMATCH => (string) __('dberp.site_onboarding.status_labels.domain_mismatch'),
            $normalized === self::CANONICAL_INSTANCE_MISMATCH => (string) __('dberp.site_onboarding.status_labels.instance_mismatch'),
            $normalized === self::CANONICAL_NO_ACTIVE_SITE_BINDING => (string) __('dberp.site_onboarding.status_labels.no_active_site_binding'),
            default => (string) __('dberp.site_onboarding.status_labels.unknown'),
        };
    }

    public function badgeColor(?string $status): string
    {
        $normalized = strtolower(trim((string) $status));

        return match (true) {
            in_array($normalized, ['bound', 'active', 'passed', self::CANONICAL_BINDING_ACTIVE], true) => 'success',
            in_array($normalized, [
                'validating',
                'pending',
                'executing',
                self::CANONICAL_AWAIT_ACCESS_CONFIRM,
                'await_fixed_file_or_callback',
                'await_local_validation',
                'await_local_validation_already',
                'validation_passed_await_platform',
                self::CANONICAL_PENDING_MANUAL_REVIEW,
            ], true) => 'warning',
            in_array($normalized, [
                'failed',
                'rejected',
                'expired',
                self::CANONICAL_CALLBACK_FAILED,
                self::CANONICAL_ALREADY_BOUND,
                self::CANONICAL_DOMAIN_MISMATCH,
                self::CANONICAL_INSTANCE_MISMATCH,
                'validation_failed',
                'validation_failed_review_or_reapply',
                'auto_failed',
            ], true) => 'danger',
            $normalized === self::CANONICAL_NO_ACTIVE_SITE_BINDING => 'gray',
            default => 'gray',
        };
    }

    public function isBound(?string $status): bool
    {
        $normalized = strtolower(trim((string) $status));

        return in_array($normalized, ['bound', 'active', 'passed', self::CANONICAL_BINDING_ACTIVE], true);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    private function resolveCanonicalStatus(array $data, ?string $nextAction): ?string
    {
        if ($nextAction !== null && $nextAction !== '') {
            return $this->mapNextActionToCanonical($nextAction);
        }

        if ($this->booleanFromMixed($data['is_bound'] ?? null) === true) {
            $applicationStatus = $this->normalizedString($data['application_status'] ?? null);

            if ($applicationStatus === self::CANONICAL_BINDING_ACTIVE) {
                return self::CANONICAL_BINDING_ACTIVE;
            }

            return self::CANONICAL_BOUND;
        }

        $taskStatus = $this->normalizedString($data['task_status'] ?? null);

        if ($taskStatus !== null) {
            $fromTask = $this->mapOpenHostStatusToCanonical($taskStatus);

            if ($fromTask !== null && $this->taskStatusShouldTakePriority($fromTask)) {
                return $fromTask;
            }
        }

        $applicationStatus = $this->normalizedString($data['application_status'] ?? null);

        if ($applicationStatus !== null) {
            $fromApplication = $this->mapOpenHostStatusToCanonical($applicationStatus);

            if ($fromApplication !== null) {
                return $fromApplication;
            }
        }

        if ($taskStatus !== null) {
            $fromTask ??= $this->mapOpenHostStatusToCanonical($taskStatus);

            if ($fromTask !== null) {
                return $fromTask;
            }
        }

        foreach (['binding_status', 'status', 'site_binding_status', 'validation_status'] as $key) {
            $value = $this->normalizedString($data[$key] ?? null);

            if ($value !== null) {
                return $this->mapLegacyStatusToCanonical($value);
            }
        }

        return null;
    }

    private function mapNextActionToCanonical(string $nextAction): string
    {
        $normalized = strtolower(trim($nextAction));

        return match ($normalized) {
            'binding_active' => self::CANONICAL_BINDING_ACTIVE,
            'no_active_site_binding' => self::CANONICAL_NO_ACTIVE_SITE_BINDING,
            'pending_manual_review' => self::CANONICAL_PENDING_MANUAL_REVIEW,
            'await_access_confirm' => self::CANONICAL_AWAIT_ACCESS_CONFIRM,
            'validation_failed_review_or_reapply' => 'validation_failed_review_or_reapply',
            'validation_passed_await_platform' => 'validation_passed_await_platform',
            'await_fixed_file_or_callback' => 'await_fixed_file_or_callback',
            'await_local_validation', 'await_local_validation_already' => 'await_local_validation',
            default => $normalized,
        };
    }

    private function mapOpenHostStatusToCanonical(string $status): ?string
    {
        $normalized = strtolower(trim($status));

        return match ($normalized) {
            'binding_active' => self::CANONICAL_BINDING_ACTIVE,
            'pending_manual_review' => self::CANONICAL_PENDING_MANUAL_REVIEW,
            'callback_failed' => self::CANONICAL_CALLBACK_FAILED,
            'expired' => self::CANONICAL_EXPIRED,
            'validating', 'pending_validation', 'pending_binding_confirm' => 'validating',
            'executing', 'pending_execution' => 'executing',
            'completed', 'passed' => 'passed',
            'auto_failed', 'rejected', 'failed' => 'failed',
            'unbound' => self::CANONICAL_NO_ACTIVE_SITE_BINDING,
            default => $normalized !== '' ? $normalized : null,
        };
    }

    private function mapLegacyStatusToCanonical(string $status): string
    {
        $mapped = $this->mapOpenHostStatusToCanonical($status);

        return $mapped ?? strtolower(trim($status));
    }

    private function taskStatusShouldTakePriority(string $canonicalTaskStatus): bool
    {
        return in_array($canonicalTaskStatus, [
            self::CANONICAL_EXPIRED,
            self::CANONICAL_CALLBACK_FAILED,
            self::CANONICAL_PENDING_MANUAL_REVIEW,
            'failed',
            'passed',
            self::CANONICAL_BOUND,
            self::CANONICAL_BINDING_ACTIVE,
        ], true);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    private function extractNextAction(array $data): ?string
    {
        $value = $data['next_action'] ?? null;

        if (! is_string($value) || trim($value) === '') {
            return null;
        }

        return strtolower(trim($value));
    }

    /**
     * @param  array<string, mixed>  $data
     */
    private function extractNextActionHint(array $data): ?string
    {
        $value = $data['next_action_hint'] ?? null;

        if (! is_string($value) || trim($value) === '') {
            return null;
        }

        $trimmed = trim($value);

        if ($this->containsTechnicalLeakage($trimmed)) {
            return null;
        }

        return $trimmed;
    }

    private function resolveDisplayLabel(?string $canonical, ?string $nextActionHint): string
    {
        if ($nextActionHint !== null && $nextActionHint !== '') {
            return $nextActionHint;
        }

        return $this->label($canonical);
    }

    private function containsTechnicalLeakage(string $value): bool
    {
        $lower = strtolower($value);

        foreach ([
            'request_id',
            'task_status',
            'application_status',
            'error_code',
            'dberp_support_center',
            'host_token',
            'instance_id',
        ] as $needle) {
            if (str_contains($lower, $needle)) {
                return true;
            }
        }

        return preg_match('/\b[a-z]+_[a-z0-9_]+\b/', $lower) === 1
            && str_contains($lower, '_')
            && ! str_contains($value, '。')
            && ! str_contains($value, '，');
    }

    private function normalizedString(mixed $value): ?string
    {
        if (! is_string($value)) {
            return null;
        }

        $trimmed = strtolower(trim($value));

        return $trimmed === '' ? null : $trimmed;
    }

    private function booleanFromMixed(mixed $value): ?bool
    {
        if (is_bool($value)) {
            return $value;
        }

        if (is_int($value) || is_float($value)) {
            return (bool) $value;
        }

        if (! is_string($value)) {
            return null;
        }

        $normalized = strtolower(trim($value));

        return match ($normalized) {
            '1', 'true', 'yes', 'on' => true,
            '0', 'false', 'no', 'off' => false,
            default => null,
        };
    }
}
