<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\HostSystemUpdatePostApplyException;
use App\Models\HostSystemUpdateExecution;
use Illuminate\Database\Migrations\Migrator;
use Illuminate\Support\Facades\File;
use Throwable;

/**
 * 保存迁移开始前的 migrations 表快照，用于决定文件/vendor 是否仍可安全回滚。
 */
final class HostSystemUpdateMigrationStateStore
{
    private const FILE_NAME = 'migration-baseline.json';

    public function __construct(private readonly Migrator $migrator) {}

    public function hasBaseline(HostSystemUpdateExecution $execution): bool
    {
        return is_file($this->path($execution));
    }

    public function captureBaseline(HostSystemUpdateExecution $execution): void
    {
        // Watchdog / 网关超时后可能带着空的 migrationProgress 再次进入迁移阶段。
        // 不得覆盖首次快照，否则回滚会把「迁移后」当成「迁移前」。
        if ($this->hasBaseline($execution)) {
            return;
        }

        $repository = $this->migrator->getRepository();
        $ran = $repository->repositoryExists() ? $repository->getRan() : [];
        $path = $this->path($execution);
        HostSystemUpdateWorkspaceStorage::ensureDirectoryExists(dirname($path));
        try {
            File::replace($path, json_encode([
                'ran' => array_values($ran),
                'captured_at' => now()->utc()->toIso8601String(),
            ], JSON_THROW_ON_ERROR).PHP_EOL);
        } catch (Throwable $exception) {
            throw HostSystemUpdatePostApplyException::migrationFailed($exception);
        }
    }

    public function hasAppliedMigrationsSinceBaseline(HostSystemUpdateExecution $execution): bool
    {
        $baseline = $this->readBaseline($execution);

        if ($baseline === null) {
            // 无法证明数据库未变化时按不安全处理。
            return true;
        }

        $repository = $this->migrator->getRepository();

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

        return array_diff($repository->getRan(), $baseline) !== [];
    }

    /** @return list<string>|null */
    private function readBaseline(HostSystemUpdateExecution $execution): ?array
    {
        $path = $this->path($execution);

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

        $payload = json_decode((string) file_get_contents($path), true);
        $ran = is_array($payload) ? ($payload['ran'] ?? null) : null;

        if (! is_array($ran)) {
            return null;
        }

        return array_values(array_filter($ran, static fn (mixed $name): bool => is_string($name)));
    }

    private function path(HostSystemUpdateExecution $execution): string
    {
        return HostSystemUpdateWorkspaceStorage::executionWorkspaceDirectory((string) $execution->execution_no)
            .DIRECTORY_SEPARATOR.self::FILE_NAME;
    }
}
