<?php

declare(strict_types=1);

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

namespace App\Exceptions;

use Error;
use ErrorException;
use Illuminate\Contracts\Container\BindingResolutionException;
use InvalidArgumentException;
use Throwable;

/**
 * 在线更新分批替换时，新旧 PHP 文件会短暂共存。
 *
 * Web 下一步会重新 bootstrap Filament；此时出现的「方法/类/视图不存在」是换文件顺序问题，
 * 不是业务 bug。仅在系统更新维护模式下吞掉这类错误，让 Livewire 继续跑下一批替换。
 *
 * NOTE: 本类放在 app/Exceptions，旧宿主按字母序会早于 app/Providers 落盘。
 */
final class SystemUpdateMixedStateErrorGuard
{
    private const UPDATE_CENTER_PATH_NEEDLE = 'host-system-update-center';

    /**
     * @var list<string>
     */
    private const PHP_ERROR_MESSAGE_NEEDLES = [
        'Call to undefined method',
        'Call to undefined function',
        'Class "',
        'Interface "',
        'Trait "',
        'Unknown named parameter',
    ];

    /**
     * @var list<string>
     */
    private const EXECUTOR_NEEDLES = [
        'HostSystemUpdateFileReplacer',
        'HostSystemUpdateFileReplaceBatchRunner',
        'HostSystemUpdateFileDeletionService',
        'HostSystemUpdateStepRunner',
        'HostSystemUpdateVendorReplacer',
        'HostSystemUpdateVendorBundleReplacer',
    ];

    public static function shouldSuppress(Throwable $throwable): bool
    {
        if (! self::isSystemUpdateMaintenance()) {
            return false;
        }

        if (! self::isBenignMixedStateError($throwable)) {
            return false;
        }

        return ! self::isInsideUpdateExecutor($throwable);
    }

    public static function isBenignMixedStateError(Throwable $throwable): bool
    {
        $current = $throwable;

        do {
            if (self::isDirectMixedStateError($current)) {
                return true;
            }

            $current = $current->getPrevious();
        } while ($current instanceof Throwable);

        return false;
    }

    private static function isDirectMixedStateError(Throwable $throwable): bool
    {
        $message = $throwable->getMessage();

        if ($throwable instanceof BindingResolutionException) {
            return str_contains($message, 'Target class [')
                && str_contains($message, '] does not exist.');
        }

        if ($throwable instanceof InvalidArgumentException) {
            return str_starts_with($message, 'View [')
                && str_ends_with($message, '] not found.');
        }

        return ($throwable instanceof Error || $throwable instanceof ErrorException)
            && self::containsPhpErrorMessageNeedle($message);
    }

    private static function containsPhpErrorMessageNeedle(string $message): bool
    {
        foreach (self::PHP_ERROR_MESSAGE_NEEDLES as $needle) {
            if (str_contains($message, $needle)) {
                return true;
            }
        }

        return false;
    }

    private static function isSystemUpdateMaintenance(): bool
    {
        $path = storage_path('framework/down');

        if (! is_file($path)) {
            return false;
        }

        /**
         * Laravel 的 down 文件可能在请求并发期间被删除；此处不能让读取告警覆盖原始异常。
         */
        $contents = @file_get_contents($path);
        $payload = is_string($contents) ? json_decode($contents, true) : null;
        $exceptPaths = is_array($payload) ? ($payload['except'] ?? null) : null;

        if (! is_array($exceptPaths)) {
            return false;
        }

        foreach ($exceptPaths as $exceptPath) {
            if (is_string($exceptPath) && str_contains($exceptPath, self::UPDATE_CENTER_PATH_NEEDLE)) {
                return true;
            }
        }

        return false;
    }

    private static function isInsideUpdateExecutor(Throwable $throwable): bool
    {
        $haystack = $throwable->getFile()."\n".$throwable->getTraceAsString();

        foreach (self::EXECUTOR_NEEDLES as $needle) {
            if (str_contains($haystack, $needle)) {
                return true;
            }
        }

        return false;
    }
}
