<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Enums\SupportCenter\HostSystemUpdateExecutionStep;
use App\Enums\SupportCenter\HostSystemUpdateStepPhase;
use App\Models\HostSystemUpdateExecution;

/**
 * 分批替换 manifest 文件并执行 delete_files。
 */
final class HostSystemUpdateFileReplaceBatchRunner
{
    private const LIST_KEY_REPLACE = 'replace_files';

    private const LIST_KEY_DELETE = 'delete_files';

    public function __construct(
        private readonly HostSystemUpdateExecutionStateMachine $stateMachine,
        private readonly HostSystemUpdateExecutionContextResolver $contextResolver,
        private readonly HostSystemUpdateFileReplacer $fileReplacer,
        private readonly HostSystemUpdateFileDeletionService $fileDeletionService,
        private readonly HostSystemUpdateExecutionBatchProgressStore $progressStore,
        private readonly HostSystemUpdateExecutionLogger $executionLogger,
        private readonly SupportCenterAuditLogger $auditLogger,
    ) {}

    public function runNextBatch(HostSystemUpdateExecution $execution): HostSystemUpdateBatchPhaseResult
    {
        $phase = HostSystemUpdateStepPhase::ReplaceFiles->value;
        $existing = $this->progressStore->read((string) $execution->execution_no, $phase);

        if ($existing?->isCompleted() === true) {
            return new HostSystemUpdateBatchPhaseResult(true, $existing, $execution);
        }

        $batchSize = max(1, (int) config('dberp.system_update.file_batch_size', 100));

        if ($existing === null) {
            $execution = $this->stateMachine->advanceStep(
                $execution->fresh() ?? $execution,
                HostSystemUpdateExecutionStep::ReplaceFiles,
                (string) __('dberp.system_update.file_execution.log.apply_files'),
                progressPercent: 50,
            );

            $manifest = $this->contextResolver->manifest($execution);
            $replacePaths = $this->normalizePathList($manifest[HostSystemUpdateManifestContract::FILES] ?? []);
            $deletePaths = $this->normalizePathList($manifest[HostSystemUpdateManifestContract::DELETE_FILES] ?? []);

            $this->progressStore->writeStableList((string) $execution->execution_no, self::LIST_KEY_REPLACE, $replacePaths);
            $this->progressStore->writeStableList((string) $execution->execution_no, self::LIST_KEY_DELETE, $deletePaths);

            $this->contextResolver->resolveCompletedFileSnapshotBackup($execution);
            $package = $this->contextResolver->resolveReadyPackage($execution);

            $this->auditLogger->logSystemUpdateFileReplacementStarted($execution, $package, count($replacePaths));
            $this->executionLogger->logStepStarted(
                $execution,
                HostSystemUpdateExecutionStep::ReplaceFiles->value,
                (string) __('dberp.system_update.file_execution.log.replace_started'),
                ['file_count' => count($replacePaths)],
            );

            $existing = new HostSystemUpdateBatchProgress(
                phase: $phase,
                status: HostSystemUpdateBatchProgress::STATUS_IN_PROGRESS,
                total: count($replacePaths) + count($deletePaths),
                processed: 0,
                cursor: 0,
                current: null,
                batchSize: $batchSize,
                subStep: HostSystemUpdateBatchProgress::SUBSTEP_REPLACE,
            );
        }

        $subStep = $existing->subStep ?? HostSystemUpdateBatchProgress::SUBSTEP_REPLACE;

        if ($subStep === HostSystemUpdateBatchProgress::SUBSTEP_REPLACE) {
            return $this->runReplaceSubStep($execution, $existing);
        }

        return $this->runDeleteSubStep($execution, $existing);
    }

    public function snapshotProgress(HostSystemUpdateExecution $execution): ?HostSystemUpdateBatchProgress
    {
        return $this->progressStore->read(
            (string) $execution->execution_no,
            HostSystemUpdateStepPhase::ReplaceFiles->value,
        );
    }

    private function runReplaceSubStep(
        HostSystemUpdateExecution $execution,
        HostSystemUpdateBatchProgress $progress,
    ): HostSystemUpdateBatchPhaseResult {
        $paths = $this->progressStore->readStableList((string) $execution->execution_no, self::LIST_KEY_REPLACE);
        $slice = array_slice($paths, $progress->cursor, $progress->batchSize);
        $manifest = $this->contextResolver->manifest($execution);
        $stagingDirectory = $this->contextResolver->stagingDirectory($execution);
        $partial = $progress->processed > 0;

        foreach ($slice as $path) {
            $this->fileReplacer->replaceManifestFile($execution, $manifest, $stagingDirectory, $path, $partial);
            $partial = true;
        }

        $processed = min(count($paths), $progress->cursor + count($slice));
        $current = $slice !== [] ? (string) end($slice) : $progress->current;

        if ($processed < count($paths)) {
            $updated = $progress->withCursor($progress->cursor + count($slice), $processed, $current);
            $this->progressStore->save((string) $execution->execution_no, $updated);

            return new HostSystemUpdateBatchPhaseResult(false, $updated, $execution);
        }

        $deletePaths = $this->progressStore->readStableList((string) $execution->execution_no, self::LIST_KEY_DELETE);

        if ($deletePaths === []) {
            return $this->completeReplacePhase($execution, $progress, $processed);
        }

        $deleteProgress = new HostSystemUpdateBatchProgress(
            phase: $progress->phase,
            status: HostSystemUpdateBatchProgress::STATUS_IN_PROGRESS,
            total: count($paths) + count($deletePaths),
            processed: count($paths),
            cursor: 0,
            current: null,
            batchSize: $progress->batchSize,
            subStep: HostSystemUpdateBatchProgress::SUBSTEP_DELETE,
        );
        $this->progressStore->save((string) $execution->execution_no, $deleteProgress);

        return new HostSystemUpdateBatchPhaseResult(false, $deleteProgress, $execution);
    }

    private function runDeleteSubStep(
        HostSystemUpdateExecution $execution,
        HostSystemUpdateBatchProgress $progress,
    ): HostSystemUpdateBatchPhaseResult {
        $paths = $this->progressStore->readStableList((string) $execution->execution_no, self::LIST_KEY_DELETE);
        $replaceCount = count($this->progressStore->readStableList((string) $execution->execution_no, self::LIST_KEY_REPLACE));
        $slice = array_slice($paths, $progress->cursor, $progress->batchSize);

        if ($progress->cursor === 0 && $paths !== []) {
            $package = $this->contextResolver->resolveReadyPackage($execution);
            $this->auditLogger->logSystemUpdateFileDeletionStarted($execution, $package, count($paths));
            $this->executionLogger->logStepStarted(
                $execution,
                HostSystemUpdateExecutionStep::ReplaceFiles->value,
                (string) __('dberp.system_update.file_execution.log.delete_started'),
                ['file_count' => count($paths)],
            );
        }

        $backedUpPaths = $this->contextResolver->backedUpPaths($execution);
        $partial = $progress->processed > 0;

        foreach ($slice as $path) {
            $this->fileDeletionService->deleteManifestFile($path, $backedUpPaths, $partial);
            $partial = true;
        }

        $processed = $replaceCount + min(count($paths), $progress->cursor + count($slice));
        $current = $slice !== [] ? (string) end($slice) : $progress->current;

        if ($processed < $replaceCount + count($paths)) {
            $updated = $progress->withCursor($progress->cursor + count($slice), $processed, $current);
            $this->progressStore->save((string) $execution->execution_no, $updated);

            return new HostSystemUpdateBatchPhaseResult(false, $updated, $execution);
        }

        return $this->completeReplacePhase($execution, $progress, $processed);
    }

    private function completeReplacePhase(
        HostSystemUpdateExecution $execution,
        HostSystemUpdateBatchProgress $progress,
        int $processed,
    ): HostSystemUpdateBatchPhaseResult {
        $package = $this->contextResolver->resolveReadyPackage($execution);

        $this->executionLogger->logStepCompleted(
            $execution,
            HostSystemUpdateExecutionStep::ReplaceFiles->value,
            (string) __('dberp.system_update.file_execution.log.replace_completed', [
                'count' => $processed,
            ]),
            ['replaced_count' => $processed],
            now()->utc(),
        );

        $this->auditLogger->logSystemUpdateFileReplacementCompleted($execution, $package, [
            'replaced_count' => $processed,
            'skipped_count' => 0,
            'file_results' => [],
        ]);

        $deletePaths = $this->progressStore->readStableList((string) $execution->execution_no, self::LIST_KEY_DELETE);

        if ($deletePaths !== []) {
            $this->executionLogger->logStepCompleted(
                $execution,
                HostSystemUpdateExecutionStep::ReplaceFiles->value,
                (string) __('dberp.system_update.file_execution.log.delete_completed', [
                    'count' => count($deletePaths),
                ]),
                ['deleted_count' => count($deletePaths)],
                now()->utc(),
            );
            $this->auditLogger->logSystemUpdateFileDeletionCompleted($execution, $package, [
                'deleted_count' => count($deletePaths),
                'skipped_count' => 0,
                'missing_count' => 0,
                'file_results' => [],
            ]);
        }

        $completed = $progress->markCompleted();
        $this->progressStore->save((string) $execution->execution_no, $completed);
        $this->progressStore->clearList((string) $execution->execution_no, self::LIST_KEY_REPLACE);
        $this->progressStore->clearList((string) $execution->execution_no, self::LIST_KEY_DELETE);

        return new HostSystemUpdateBatchPhaseResult(
            true,
            $completed,
            $execution->fresh(['package', 'backups']),
        );
    }

    /**
     * @return list<string>
     */
    private function normalizePathList(mixed $items): array
    {
        if (! is_array($items)) {
            return [];
        }

        $paths = [];

        foreach ($items as $path) {
            if (is_string($path) && $path !== '') {
                $paths[] = $path;
            }
        }

        sort($paths, SORT_STRING);

        return array_values(array_unique($paths));
    }
}
