<?php

declare(strict_types=1);

namespace App\Services\Install;

use App\Models\DberpAdminRole;
use App\Models\DberpSystemProfile;
use App\Models\User;
use App\Services\DberpAdminAccessService;
use App\Services\Install\Contracts\WebInstallStagedPhaseExecutor;
use App\Services\SupportCenter\HostSiteVerificationStaticPublisher;
use App\Support\DberpProductBranding;
use DomainException;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schema;
use JsonException;
use RuntimeException;
use Throwable;

/**
 * Web 安装向导编排：迁移前数据库状态复核、迁移（含基线数据迁移）、管理员与已安装标记（不写入 DBCore）。
 */
final class DBErpWebInstallCoordinator implements WebInstallStagedPhaseExecutor
{
    public function __construct(
        private readonly DberpAdminAccessService $dberpAdminAccessService,
        private readonly EnvironmentFileWriter $environmentFileWriter,
        private readonly DBErpInstallStatus $dberpInstallStatus,
        private readonly InstallerDatabaseStateChecker $installerDatabaseStateChecker,
        private readonly InstallerHostedSchemaResetService $installerHostedSchemaResetService,
        private readonly DBErpInstallLogger $installLogger,
        private readonly InstallPublicStorageLinkGuard $publicStorageLinkGuard,
        private readonly WebInstallOptionalStorageLinkStep $optionalStorageLinkStep,
        private readonly WebInstallFailureClassifier $failureClassifier,
        private readonly HostSiteVerificationStaticPublisher $siteVerificationStaticPublisher,
        private readonly DberpProductBranding $productBranding,
    ) {}

    /**
     * @param  array{
     *     system_url: string,
     *     timezone: string,
     *     admin_name: string,
     *     login_account: string,
     *     admin_email?: string|null,
     *     password: string,
     *     reinstall_reset?: bool,
     * }  $payload
     * @return list<string> 可选收尾警告（空列表表示无警告）
     *
     * @throws RuntimeException
     * @throws DomainException
     * @throws DatabaseNotInstallableException
     * @throws WebInstallFinalizationIncompleteException
     */
    public function execute(array $payload): void
    {
        $this->executeWithWarnings($payload);
    }

    /**
     * @param  array{
     *     system_url: string,
     *     timezone: string,
     *     admin_name: string,
     *     login_account: string,
     *     admin_email?: string|null,
     *     password: string,
     *     reinstall_reset?: bool,
     * }  $payload
     * @return list<string>
     */
    public function executeWithWarnings(array $payload): array
    {
        $phaseTracker = new WebInstallPhaseTracker;
        $finalizationWarnings = new WebInstallFinalizationWarnings;

        $this->installLogger->installSessionStarted();

        try {
            $this->enterPhase($phaseTracker, WebInstallPhase::CheckingDatabase);
            $this->executeCheckingDatabasePhase($payload);

            $this->enterPhase($phaseTracker, WebInstallPhase::WritingEnv);
            $this->executeWritingEnvPhase($payload);

            $this->enterPhase($phaseTracker, WebInstallPhase::Migrating);
            $this->executeMigratingPhase();

            $this->enterPhase($phaseTracker, WebInstallPhase::CreatingAdmin);
            $this->executeCreatingAdminPhase($payload);

            $this->enterPhase($phaseTracker, WebInstallPhase::UpdatingSystemProfile);
            $this->executeUpdatingSystemProfilePhase();

            $this->enterPhase($phaseTracker, WebInstallPhase::WritingInstallMarkers);
            $this->executeWritingInstallMarkersPhase($payload);

            $this->enterPhase($phaseTracker, WebInstallPhase::RunningOptionalFinalizers);
            foreach ($this->executeOptionalFinalizersPhase() as $warning) {
                $finalizationWarnings->add($warning);
            }

            $this->enterPhase($phaseTracker, WebInstallPhase::Completed);
            $this->executeCompletedPhase($finalizationWarnings->all());

            return $finalizationWarnings->all();
        } catch (JsonException $e) {
            $this->installLogger->technicalFailure('env_write', '写入 .env 时发生 JSON 编码异常', $e);

            throw $this->failureClassifier->toUserFacingException(
                new RuntimeException('安装配置保存失败，请检查项目目录和 .env 文件写入权限。', 0, $e),
                $phaseTracker,
            );
        } catch (Throwable $e) {
            throw $this->failureClassifier->toUserFacingException($e, $phaseTracker);
        }
    }

    /**
     * @param  array{reinstall_reset?: bool}  $payload
     */
    public function executeCheckingDatabasePhase(array $payload): void
    {
        $reinstallReset = (bool) ($payload['reinstall_reset'] ?? false);

        $this->publicStorageLinkGuard->removeInvalidPublicStoragePath();
        $this->assertInstallable();
        $this->ensureDatabaseSafeForInstallationBeforeMigration('向导开始时（首次检查）', $reinstallReset);
        $this->ensureDatabaseSafeForInstallationBeforeMigration('执行迁移之前（二次复核）', false);
    }

    /**
     * @param  array{system_url: string, timezone: string}  $payload
     */
    public function executeWritingEnvPhase(array $payload): void
    {
        $this->environmentFileWriter->ensureApplicationKey(base_path());
        $this->mergeRuntimeEnv($payload, includeInstalledFlag: false);
    }

    public function executeMigratingPhase(): void
    {
        $this->runMigrations();

        $this->installLogger->stepProgress(
            'post_migrate',
            '数据库迁移已成功完成（含基线字典与编号规则），继续创建超级管理员与系统档案',
            [],
        );
    }

    /**
     * @param  array{
     *     admin_name: string,
     *     login_account: string,
     *     admin_email?: string|null,
     *     password: string,
     * }  $payload
     */
    public function executeCreatingAdminPhase(array $payload): void
    {
        $this->assertAdminBootstrapTablesReady();

        $login = trim((string) ($payload['login_account'] ?? ''));
        if ($login !== '' && User::query()->where('login_account', $login)->exists()) {
            $this->installLogger->stepProgress('admin_bootstrap', '超级管理员账号已存在，跳过重复创建', [
                'login_account' => $login,
            ]);

            return;
        }

        $this->assertAdministratorIdentifiersAvailable($payload);

        try {
            $this->dberpAdminAccessService->bootstrapInstallationSuperAdministrator([
                'name' => $payload['admin_name'],
                'login_account' => $payload['login_account'],
                'email' => $payload['admin_email'] ?? null,
                'password' => $payload['password'],
                'nickname' => $payload['admin_name'],
            ]);
        } catch (DomainException $e) {
            $this->installLogger->businessRuleBlocked('admin_bootstrap', $e->getMessage());
            throw $e;
        } catch (Throwable $e) {
            $this->installLogger->technicalFailure('admin_bootstrap', '创建管理员账号阶段发生异常', $e);
            throw new RuntimeException('创建管理员账号失败，请检查登录账号与邮箱是否已被占用。', 0, $e);
        }
    }

    public function executeUpdatingSystemProfilePhase(): void
    {
        $brandName = $this->productBranding->defaultBrandName();

        DberpSystemProfile::query()->updateOrCreate(
            ['id' => 1],
            array_merge(DberpSystemProfile::defaultAttributes(), [
                'company_name' => $brandName,
                'system_display_name' => $brandName,
            ]),
        );

        $this->installLogger->stepProgress('system_profile', '系统档案已更新', [
            'system_display_name' => $brandName,
        ]);
    }

    /**
     * @param  array{system_url: string, timezone: string}  $payload
     */
    public function executeWritingInstallMarkersPhase(array $payload): void
    {
        if ($this->dberpInstallStatus->isInstalled()) {
            $this->installLogger->stepProgress(
                'installed_marker',
                '已检测到安装标记或锁文件，跳过重复写入',
                [],
            );

            return;
        }

        $this->mergeRuntimeEnv($payload, includeInstalledFlag: true);
        $this->dberpInstallStatus->createLock();
        $this->siteVerificationStaticPublisher->publishFromSystemUrl($payload['system_url']);

        $this->installLogger->stepProgress(
            'installed_marker',
            '已写入 DBERP_INSTALLED 与 installed.lock（必选步骤已完成，开始可选收尾）',
            [],
        );
    }

    /**
     * @return list<string>
     */
    public function executeOptionalFinalizersPhase(): array
    {
        $finalizationWarnings = new WebInstallFinalizationWarnings;
        $this->optionalStorageLinkStep->run($finalizationWarnings);
        $this->runOptionalArtisanCommand('optimize:clear', []);

        return $finalizationWarnings->all();
    }

    /**
     * @param  list<string>  $finalizationWarnings
     */
    public function executeCompletedPhase(array $finalizationWarnings = []): void
    {
        $this->installLogger->installCompletedSuccessfully($finalizationWarnings);
    }

    private function enterPhase(WebInstallPhaseTracker $phaseTracker, WebInstallPhase $phase): void
    {
        $phaseTracker->enter($phase);
        $this->installLogger->phaseEntered($phase);
    }

    /**
     * @param  array{system_url: string, timezone: string}  $payload
     */
    private function mergeRuntimeEnv(array $payload, bool $includeInstalledFlag): void
    {
        $brandName = $this->productBranding->defaultBrandName();

        $values = [
            'APP_ENV' => 'production',
            'APP_DEBUG' => 'false',
            'APP_NAME' => $brandName,
            'APP_URL' => $payload['system_url'],
            'APP_TIMEZONE' => $payload['timezone'],
            'DBERP_SYSTEM_NAME' => $brandName,
        ];

        if ($includeInstalledFlag) {
            $values['DBERP_INSTALLED'] = true;
        }

        try {
            $this->environmentFileWriter->mergeKeysIntoEnv(base_path(), $values);
        } catch (JsonException $e) {
            $this->installLogger->technicalFailure('env_write', '写入 .env 时发生 JSON 编码异常', $e);
            throw $e;
        } catch (RuntimeException $e) {
            $this->installLogger->technicalFailure('env_write', '写入 .env 失败', $e);
            throw new RuntimeException('安装配置保存失败，请检查项目目录和 .env 文件写入权限。', 0, $e);
        }
    }

    /**
     * @param  array<string, mixed>  $parameters
     */
    private function runOptionalArtisanCommand(string $command, array $parameters): void
    {
        $this->installLogger->stepProgress('artisan', '开始执行可选收尾：php artisan '.$command, [
            'command' => $command,
            'optional' => true,
        ]);

        try {
            $exitCode = Artisan::call($command, $parameters);
            $output = Artisan::output();
            $this->installLogger->commandFinished($command, 'Artisan '.$command.' 已结束（可选收尾）', $exitCode, $output);

            $this->installLogger->optionalCommandOutcome(
                $command,
                $exitCode === 0 ? 'executed' : 'failed',
                $exitCode,
                $output,
            );
        } catch (Throwable $e) {
            $this->installLogger->technicalFailure('optional_finalizer', '可选收尾命令异常：'.$command, $e);
            $this->installLogger->optionalCommandOutcome(
                $command,
                'failed',
                null,
                $e->getMessage(),
                ['error_output' => $e->getMessage()],
            );
        }
    }

    /**
     * @param  array<string, mixed>  $parameters
     *
     * @throws RuntimeException
     */
    private function runRequiredArtisanCommand(string $command, array $parameters, string $failureMessage): void
    {
        $this->installLogger->stepProgress('artisan', '开始执行 php artisan '.$command, [
            'command' => $command,
        ]);

        $exitCode = Artisan::call($command, $parameters);
        $output = Artisan::output();
        $this->installLogger->commandFinished($command, 'Artisan '.$command.' 已结束', $exitCode, $output);

        if ($exitCode !== 0) {
            throw new RuntimeException($failureMessage);
        }
    }

    /**
     * @param  array{login_account: string, admin_email?: string|null}  $payload
     */
    private function assertAdministratorIdentifiersAvailable(array $payload): void
    {
        $login = trim((string) ($payload['login_account'] ?? ''));
        if ($login !== '' && User::query()->where('login_account', $login)->exists()) {
            $this->installLogger->businessRuleBlocked('admin_identifiers', '登录账号已被使用');
            throw new DomainException('登录账号已被使用，请更换后再试。');
        }

        $email = $payload['admin_email'] ?? null;
        if (filled($email) && User::query()->where('email', (string) $email)->exists()) {
            $this->installLogger->businessRuleBlocked('admin_identifiers', '邮箱已被使用');
            throw new DomainException('该邮箱已被使用，请更换后再试。');
        }
    }

    private function assertAdminBootstrapTablesReady(): void
    {
        if (Schema::hasTable('users')) {
            return;
        }

        $this->installLogger->stepProgress('admin_bootstrap', '创建超级管理员前检测到 users 表不存在', [
            'required_table' => 'users',
        ]);

        throw new DomainException(WebInstallUserMessages::ADMIN_BOOTSTRAP_TABLES_MISSING);
    }

    private function assertInstallable(): void
    {
        if ($this->dberpInstallStatus->isInstalled()) {
            $this->installLogger->installAborted('already_installed', '安装请求被拒绝：已检测到已安装标记或锁文件', []);
            throw new RuntimeException('系统已完成安装，无法重复执行初始化。');
        }

        if (! function_exists('symlink')) {
            $this->installLogger->installAborted('symlink_disabled', '安装请求被拒绝：PHP symlink() 不可用', [
                'disable_functions' => (string) ini_get('disable_functions'),
            ]);
            throw new RuntimeException((string) __('dberp.installer.php_symlink_blocked'));
        }

        if (Schema::hasTable('core_roles') && DberpAdminRole::query()->where('is_super_admin', true)->whereHas('users')->exists()) {
            $this->installLogger->installAborted('super_admin_exists', '安装请求被拒绝：数据库中已存在具备超级管理员权限的管理员账号', []);
            throw new RuntimeException('检测到已有具备超级管理员权限的管理员账号，请勿重复安装。');
        }
    }

    private function ensureDatabaseSafeForInstallationBeforeMigration(string $phaseLabelZh, bool $allowReinstallResetCleanup): void
    {
        $databaseState = $this->installerDatabaseStateChecker->inspectCurrentConnection();

        $this->installLogger->stepProgress('database_state', '数据库安装状态检查：'.$phaseLabelZh, [
            'phase_label' => $phaseLabelZh,
            'safe' => $databaseState['safe'],
            'checks' => $databaseState['checks'],
        ]);

        if ($databaseState['safe']) {
            return;
        }

        if ($allowReinstallResetCleanup && ($databaseState['checks']['reinstall_reset_supported'] ?? false)) {
            $this->installLogger->stepProgress(
                'database_reset',
                '已确认重新安装：正在按安全顺序移除历史数据库表与视图（仅限本系统相关对象）',
                [],
            );

            try {
                $this->installerHostedSchemaResetService->resetMysqlHostedInstallArtifacts();
            } catch (Throwable $e) {
                $this->installLogger->technicalFailure('database_reset', '重新安装清理阶段发生异常', $e);
                throw new RuntimeException('无法完成重新安装所需的数据库清理，请确认账号具备删表权限后重试。', 0, $e);
            }

            $databaseState = $this->installerDatabaseStateChecker->inspectCurrentConnection();
            $this->installLogger->stepProgress('database_state', '重新安装清理后的数据库状态复核：'.$phaseLabelZh, [
                'phase_label' => $phaseLabelZh,
                'safe' => $databaseState['safe'],
                'checks' => $databaseState['checks'],
            ]);

            if ($databaseState['safe']) {
                return;
            }
        }

        $this->installLogger->installAborted('database_state', '数据库状态不允许继续安装', [
            'phase_label' => $phaseLabelZh,
            'checks' => $databaseState['checks'],
            'summary' => $databaseState['summary'],
        ]);

        throw new DatabaseNotInstallableException(
            WebInstallUserMessages::databaseNotInstallableDetail($databaseState),
        );
    }

    private function runMigrations(): void
    {
        $this->installLogger->stepProgress('migrate', '开始执行数据库迁移（--force，不使用 migrate:fresh）');
        $this->runRequiredArtisanCommand('migrate', ['--force' => true], '数据库迁移未能完成，请确认数据库账号具备建表权限后重试。');
    }
}
