<?php

declare(strict_types=1);

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

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\HostSystemUpdateFileExecutionException;
use Symfony\Component\Process\Process;

/**
 * 在宿主项目根目录执行 composer install --no-dev（系统更新 composer_required 策略）。
 */
final class HostSystemUpdateComposerDependencyInstaller
{
    /**
     * @throws HostSystemUpdateFileExecutionException
     */
    public function install(): void
    {
        $projectRoot = base_path();

        if (! is_file($projectRoot.DIRECTORY_SEPARATOR.'composer.json')) {
            throw HostSystemUpdateFileExecutionException::prerequisitesNotMet('composer_json_missing');
        }

        if (! is_file($projectRoot.DIRECTORY_SEPARATOR.'composer.lock')) {
            throw HostSystemUpdateFileExecutionException::prerequisitesNotMet('composer_lock_missing');
        }

        $this->disablePathRepositorySymlink($projectRoot);
        $this->ensureComposerDirectories($projectRoot);

        $this->runProcess(
            ['composer', 'install', '--no-dev', '--optimize-autoloader', '--no-interaction', '--prefer-dist', '--no-scripts'],
            $projectRoot,
            ['COMPOSER_MIRROR_PATH_REPOS' => '1'],
        );

        $vendorAutoload = $projectRoot.DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php';

        if (! is_file($vendorAutoload)) {
            throw HostSystemUpdateFileExecutionException::vendorCopyFailed('vendor/autoload.php');
        }

        $this->materializeDbCoreIfNeeded($projectRoot);
    }

    public function isComposerAvailable(): bool
    {
        try {
            $process = new Process(['composer', '--version'], base_path());
            $process->setTimeout(15);
            $process->run();

            return $process->isSuccessful();
        } catch (\Throwable) {
            return false;
        }
    }

    private function disablePathRepositorySymlink(string $projectRoot): void
    {
        $composerJsonPath = $projectRoot.DIRECTORY_SEPARATOR.'composer.json';

        if (! is_file($composerJsonPath)) {
            return;
        }

        $contents = (string) file_get_contents($composerJsonPath);

        if (! str_contains($contents, '"symlink"')) {
            return;
        }

        $patched = preg_replace('/"symlink"\s*:\s*true/', '"symlink": false', $contents, 1);

        if (! is_string($patched)) {
            throw HostSystemUpdateFileExecutionException::prerequisitesNotMet('composer_json_patch_failed');
        }

        if (file_put_contents($composerJsonPath, $patched) === false) {
            throw HostSystemUpdateFileExecutionException::prerequisitesNotMet('composer_json_patch_failed');
        }
    }

    private function ensureComposerDirectories(string $projectRoot): void
    {
        foreach ([
            'bootstrap/cache',
            'storage/framework/cache',
            'storage/framework/cache/data',
            'storage/framework/views',
            'storage/logs',
        ] as $relative) {
            $directory = $projectRoot.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $relative);

            if (! is_dir($directory) && ! mkdir($directory, 0755, true) && ! is_dir($directory)) {
                throw HostSystemUpdateFileExecutionException::prerequisitesNotMet('composer_workspace_prepare_failed');
            }
        }
    }

    private function materializeDbCoreIfNeeded(string $projectRoot): void
    {
        $vendorPackagePath = $projectRoot.DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'dbcore'.DIRECTORY_SEPARATOR.'dbcore';
        $dbcoreSourcePath = realpath(dirname($projectRoot).DIRECTORY_SEPARATOR.'DBCore');

        if ($dbcoreSourcePath === false || ! is_dir($dbcoreSourcePath)) {
            return;
        }

        if (! is_dir($vendorPackagePath) && ! is_link($vendorPackagePath)) {
            return;
        }

        if (is_file($vendorPackagePath.DIRECTORY_SEPARATOR.'composer.json') && ! is_link($vendorPackagePath)) {
            return;
        }

        $stagingPath = dirname($vendorPackagePath).DIRECTORY_SEPARATOR.basename($vendorPackagePath).'.__materialize';

        if (is_dir($stagingPath)) {
            $this->removeDirectory($stagingPath);
        }

        $this->copyDirectory($dbcoreSourcePath, $stagingPath);

        $backupPath = $vendorPackagePath.'.__replace';

        if (file_exists($vendorPackagePath)) {
            if (is_link($vendorPackagePath)) {
                unlink($vendorPackagePath);
            } elseif (is_dir($vendorPackagePath)) {
                $this->removeDirectory($vendorPackagePath);
            }
        }

        if (! rename($stagingPath, $vendorPackagePath)) {
            throw HostSystemUpdateFileExecutionException::vendorCopyFailed('vendor/dbcore/dbcore');
        }

        if (is_dir($backupPath)) {
            $this->removeDirectory($backupPath);
        }
    }

    /**
     * @param  list<string>  $command
     * @param  array<string, string>  $environment
     */
    private function runProcess(array $command, string $workingDirectory, array $environment = []): void
    {
        $timeout = max(60, (int) config('dberp.system_update.composer_timeout_seconds', 900));
        $process = new Process($command, $workingDirectory, $environment);
        $process->setTimeout($timeout);
        $process->run();

        if (! $process->isSuccessful()) {
            throw HostSystemUpdateFileExecutionException::prerequisitesNotMet('composer_install_failed');
        }
    }

    private function copyDirectory(string $source, string $destination): void
    {
        if (! is_dir($destination) && ! mkdir($destination, 0755, true) && ! is_dir($destination)) {
            throw HostSystemUpdateFileExecutionException::vendorCopyFailed(basename($destination));
        }

        $items = scandir($source);

        if ($items === false) {
            throw HostSystemUpdateFileExecutionException::vendorCopyFailed(basename($source));
        }

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $sourcePath = $source.DIRECTORY_SEPARATOR.$item;
            $destinationPath = $destination.DIRECTORY_SEPARATOR.$item;

            if (is_dir($sourcePath)) {
                $this->copyDirectory($sourcePath, $destinationPath);

                continue;
            }

            if (! copy($sourcePath, $destinationPath)) {
                throw HostSystemUpdateFileExecutionException::vendorCopyFailed($item);
            }
        }
    }

    private function removeDirectory(string $directory): void
    {
        if (! is_dir($directory)) {
            return;
        }

        $items = scandir($directory);

        if ($items === false) {
            return;
        }

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $path = $directory.DIRECTORY_SEPARATOR.$item;

            if (is_dir($path)) {
                $this->removeDirectory($path);
            } elseif (is_file($path)) {
                unlink($path);
            }
        }

        @rmdir($directory);
    }
}
