<?php

declare(strict_types=1);

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

namespace App\Services\SupportCenter;

use App\Enums\SupportCenter\HostSystemUpdateStepPhase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use InvalidArgumentException;
use JsonException;

/**
 * 分步更新会话进度（storage/app/dberp/updates/sessions/{session_id}.json）。
 */
final class HostSystemUpdateStepProgressStore
{
    public function createSession(): string
    {
        $sessionId = (string) Str::uuid();
        $now = Carbon::now()->utc()->toIso8601String();
        $this->write(HostSystemUpdateStepProgress::initial($sessionId, $now));

        return $sessionId;
    }

    public function createSessionForExecution(
        string $executionNo,
        HostSystemUpdateStepPhase $phase,
        ?HostSystemUpdateMigrationProgress $migrationProgress = null,
        bool $migratePhaseComplete = false,
        ?HostSystemUpdateBatchProgress $batchProgress = null,
    ): string {
        $sessionId = (string) Str::uuid();
        $now = Carbon::now()->utc()->toIso8601String();

        $this->write(new HostSystemUpdateStepProgress(
            sessionId: $sessionId,
            phase: $phase,
            executionNo: trim($executionNo),
            terminal: false,
            failed: false,
            errorMessage: null,
            migrationProgress: $migrationProgress,
            migratePhaseComplete: $migratePhaseComplete,
            batchProgress: $batchProgress,
            startedAt: $now,
            updatedAt: $now,
        ));

        return $sessionId;
    }

    public function read(string $sessionId): ?HostSystemUpdateStepProgress
    {
        $this->assertValidSessionId($sessionId);

        $path = $this->pathFor($sessionId);
        if (! is_file($path)) {
            return null;
        }

        $raw = file_get_contents($path);
        if ($raw === false || trim($raw) === '') {
            return null;
        }

        try {
            /** @var array<string, mixed> $decoded */
            $decoded = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException) {
            return null;
        }

        return HostSystemUpdateStepProgress::fromArray($decoded);
    }

    public function save(HostSystemUpdateStepProgress $progress): HostSystemUpdateStepProgress
    {
        $this->assertValidSessionId($progress->sessionId);
        $now = Carbon::now()->utc()->toIso8601String();
        $updated = new HostSystemUpdateStepProgress(
            sessionId: $progress->sessionId,
            phase: $progress->phase,
            executionNo: $progress->executionNo,
            terminal: $progress->terminal,
            failed: $progress->failed,
            errorMessage: $progress->errorMessage,
            migrationProgress: $progress->migrationProgress,
            migratePhaseComplete: $progress->migratePhaseComplete,
            batchProgress: $progress->batchProgress,
            startedAt: $progress->startedAt,
            updatedAt: $now,
        );
        $this->write($updated);

        return $updated;
    }

    public function advancePhase(
        HostSystemUpdateStepProgress $progress,
        ?HostSystemUpdateStepPhase $nextPhase = null,
        ?string $executionNo = null,
        ?HostSystemUpdateMigrationProgress $migrationProgress = null,
        ?bool $migratePhaseComplete = null,
        ?HostSystemUpdateBatchProgress $batchProgress = null,
    ): HostSystemUpdateStepProgress {
        $resolvedPhase = $nextPhase ?? $progress->phase->next() ?? HostSystemUpdateStepPhase::Complete;

        return $this->save(new HostSystemUpdateStepProgress(
            sessionId: $progress->sessionId,
            phase: $resolvedPhase,
            executionNo: $executionNo ?? $progress->executionNo,
            terminal: $progress->terminal,
            failed: $progress->failed,
            errorMessage: $progress->errorMessage,
            migrationProgress: $migrationProgress ?? $progress->migrationProgress,
            migratePhaseComplete: $migratePhaseComplete ?? $progress->migratePhaseComplete,
            batchProgress: $batchProgress ?? $progress->batchProgress,
            startedAt: $progress->startedAt,
            updatedAt: $progress->updatedAt,
        ));
    }

    public function markTerminal(
        HostSystemUpdateStepProgress $progress,
        bool $failed,
        ?string $errorMessage = null,
    ): HostSystemUpdateStepProgress {
        return $this->save(new HostSystemUpdateStepProgress(
            sessionId: $progress->sessionId,
            phase: $progress->phase,
            executionNo: $progress->executionNo,
            terminal: true,
            failed: $failed,
            errorMessage: $errorMessage,
            migrationProgress: $progress->migrationProgress,
            migratePhaseComplete: $progress->migratePhaseComplete,
            batchProgress: $progress->batchProgress,
            startedAt: $progress->startedAt,
            updatedAt: $progress->updatedAt,
        ));
    }

    public function delete(string $sessionId): void
    {
        $this->assertValidSessionId($sessionId);

        $path = $this->pathFor($sessionId);
        if (is_file($path)) {
            File::delete($path);
        }
    }

    public function hasNonTerminalSession(): bool
    {
        $directory = HostSystemUpdateWorkspaceStorage::sessionsDirectory();

        if (! is_dir($directory)) {
            return false;
        }

        foreach (new \FilesystemIterator($directory, \FilesystemIterator::SKIP_DOTS) as $file) {
            if (! $file->isFile() || $file->getExtension() !== 'json') {
                continue;
            }

            $sessionId = $file->getBasename('.json');
            $progress = $this->read($sessionId);

            if ($progress !== null && ! $progress->terminal) {
                return true;
            }
        }

        return false;
    }

    public function latestNonTerminalForExecution(string $executionNo): ?HostSystemUpdateStepProgress
    {
        $executionNo = trim($executionNo);
        $directory = HostSystemUpdateWorkspaceStorage::sessionsDirectory();

        if ($executionNo === '' || ! is_dir($directory)) {
            return null;
        }

        $latest = null;

        foreach (new \FilesystemIterator($directory, \FilesystemIterator::SKIP_DOTS) as $file) {
            if (! $file->isFile() || $file->getExtension() !== 'json') {
                continue;
            }

            $sessionId = $file->getBasename('.json');

            if (preg_match('/^[a-f0-9\-]{36}$/i', $sessionId) !== 1) {
                continue;
            }

            $progress = $this->read($sessionId);

            if ($progress === null
                || $progress->terminal
                || $progress->executionNo !== $executionNo) {
                continue;
            }

            if ($latest === null || strcmp($progress->updatedAt, $latest->updatedAt) > 0) {
                $latest = $progress;
            }
        }

        return $latest;
    }

    private function write(HostSystemUpdateStepProgress $progress): void
    {
        HostSystemUpdateWorkspaceStorage::ensureSessionsDirectoryExists();

        $encoded = json_encode($progress->toArray(), JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
        File::replace($this->pathFor($progress->sessionId), $encoded.PHP_EOL);
    }

    private function pathFor(string $sessionId): string
    {
        return HostSystemUpdateWorkspaceStorage::sessionsDirectory().DIRECTORY_SEPARATOR.$sessionId.'.json';
    }

    private function assertValidSessionId(string $sessionId): void
    {
        if ($sessionId === '' || ! preg_match('/^[a-f0-9\-]{36}$/i', $sessionId)) {
            throw new InvalidArgumentException('Invalid system update step session id.');
        }
    }
}
