<?php

declare(strict_types=1);

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;
    }

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

        $encoded = json_encode($progress->toArray(), JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
        file_put_contents($this->pathFor($progress->sessionId), $encoded);
    }

    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.');
        }
    }
}
