<?php

declare(strict_types=1);

namespace App\Services\Install;

use Illuminate\Database\Migrations\Migrator;
use Illuminate\Support\Collection;
use Throwable;

/**
 * Web 安装向导：按小批次执行 pending 迁移（每步一次 run-step 请求）。
 */
final class WebInstallMigrationRunner
{
    public function __construct(
        private readonly Migrator $migrator,
        private readonly DBErpInstallLogger $installLogger,
    ) {}

    public function runNextBatch(string $installId): WebInstallMigrationBatchResult
    {
        $startedAt = microtime(true);
        $batchSize = $this->batchSize();
        $repository = $this->migrator->getRepository();

        if (! $repository->repositoryExists()) {
            $repository->createRepository();
        }

        $paths = $this->migrationPaths();
        /** @var array<string, string> $files */
        $files = $this->migrator->getMigrationFiles($paths);
        $ran = $repository->getRan();

        /** @var list<string> $pendingFiles */
        $pendingFiles = (new Collection($files))
            ->reject(fn (string $file, string $name): bool => in_array($name, $ran, true))
            ->values()
            ->all();

        $total = count($files);
        $completed = count(array_intersect(array_keys($files), $ran));
        $pending = count($pendingFiles);

        if ($pending === 0) {
            $progress = new WebInstallMigrationProgress(
                total: $total,
                completed: $completed,
                pending: 0,
                current: null,
                batchSize: $batchSize,
            );

            $this->installLogger->migrationBatchFinished(
                installId: $installId,
                executedMigrationNames: [],
                pendingCount: 0,
                elapsedSeconds: microtime(true) - $startedAt,
                progress: $progress,
            );

            return new WebInstallMigrationBatchResult(
                stageCompleted: true,
                progress: $progress,
            );
        }

        /** @var list<string> $batchFiles */
        $batchFiles = array_slice($pendingFiles, 0, $batchSize);
        $batchNames = array_map(
            fn (string $file): string => $this->migrator->getMigrationName($file),
            $batchFiles,
        );

        $this->installLogger->migrationBatchStarted(
            installId: $installId,
            migrationNames: $batchNames,
            pendingCount: $pending,
            batchSize: $batchSize,
            totalCount: $total,
            completedCount: $completed,
        );

        try {
            $this->migrator->requireFiles($batchFiles);
            $this->migrator->runPending($batchFiles, ['pretend' => false]);
        } catch (Throwable $e) {
            $snapshot = $this->snapshotProgress();
            $this->installLogger->migrationBatchFailed(
                installId: $installId,
                exception: $e,
                attemptedMigrationNames: $batchNames,
                pendingCount: $pending,
                elapsedSeconds: microtime(true) - $startedAt,
                progress: $snapshot,
            );

            throw $e;
        }

        $ranAfter = $repository->getRan();
        $completedAfter = count(array_intersect(array_keys($files), $ranAfter));
        $pendingAfter = max(0, $total - $completedAfter);

        $progress = new WebInstallMigrationProgress(
            total: $total,
            completed: $completedAfter,
            pending: $pendingAfter,
            current: $batchNames !== [] ? $batchNames[array_key_last($batchNames)] : null,
            batchSize: $batchSize,
        );

        $this->installLogger->migrationBatchFinished(
            installId: $installId,
            executedMigrationNames: $batchNames,
            pendingCount: $pendingAfter,
            elapsedSeconds: microtime(true) - $startedAt,
            progress: $progress,
        );

        return new WebInstallMigrationBatchResult(
            stageCompleted: $pendingAfter === 0,
            progress: $progress,
            executedMigrationNames: $batchNames,
        );
    }

    public function snapshotProgress(): WebInstallMigrationProgress
    {
        $batchSize = $this->batchSize();
        $repository = $this->migrator->getRepository();

        if (! $repository->repositoryExists()) {
            $paths = $this->migrationPaths();
            $files = $this->migrator->getMigrationFiles($paths);
            $total = count($files);

            return new WebInstallMigrationProgress(
                total: $total,
                completed: 0,
                pending: $total,
                current: null,
                batchSize: $batchSize,
            );
        }

        $paths = $this->migrationPaths();
        $files = $this->migrator->getMigrationFiles($paths);
        $ran = $repository->getRan();
        $total = count($files);
        $completed = count(array_intersect(array_keys($files), $ran));

        return new WebInstallMigrationProgress(
            total: $total,
            completed: $completed,
            pending: max(0, $total - $completed),
            current: null,
            batchSize: $batchSize,
        );
    }

    public function batchSize(): int
    {
        return max(1, (int) config('dberp.install.migration_batch_size', 10));
    }

    /**
     * 与 `php artisan migrate` 一致：合并服务提供者注册的包迁移路径与应用 `database/migrations`。
     *
     * @return list<string>
     */
    private function migrationPaths(): array
    {
        return array_values(array_unique(array_merge(
            $this->migrator->paths(),
            [database_path('migrations')],
        )));
    }
}
