<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Enums\SupportCenter\HostSystemUpdateExecutionStep;
use App\Exceptions\SupportCenter\HostSystemUpdateFileExecutionException;
use App\Exceptions\SupportCenter\HostSystemUpdateStagingException;
use App\Models\HostSystemUpdateExecution;

/**
 * 按 manifest.delete_files 删除过时文件（须已通过备份校验）。
 */
final class HostSystemUpdateFileDeletionService
{
    public function __construct(
        private readonly HostSystemUpdateExecutionContextResolver $contextResolver,
        private readonly HostSystemUpdateProtectedPathGuard $protectedPathGuard,
        private readonly HostSystemUpdateExecutionLogger $executionLogger,
        private readonly SupportCenterAuditLogger $auditLogger,
    ) {}

    /**
     * @return array{deleted_count: int, skipped_count: int, missing_count: int, file_results: list<array<string, string>>}
     */
    public function deleteFiles(HostSystemUpdateExecution $execution): array
    {
        $startedAt = now()->utc();
        $package = $this->contextResolver->resolveReadyPackage($execution);
        $manifest = $this->contextResolver->manifest($execution);
        $backedUpPaths = $this->contextResolver->backedUpPaths($execution);

        $deleteFiles = $manifest[HostSystemUpdateManifestContract::DELETE_FILES] ?? [];

        if (! is_array($deleteFiles) || $deleteFiles === []) {
            return [
                'deleted_count' => 0,
                'skipped_count' => 0,
                'missing_count' => 0,
                'file_results' => [],
            ];
        }

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

        $deletedCount = 0;
        $skippedCount = 0;
        $missingCount = 0;
        /** @var list<array<string, string>> $fileResults */
        $fileResults = [];
        $partialChangesApplied = false;

        try {
            foreach ($deleteFiles as $path) {
                if (! is_string($path)) {
                    continue;
                }

                $result = $this->deleteManifestFile($path, $backedUpPaths, $partialChangesApplied);
                $fileResults[] = $result;

                match ($result['status']) {
                    'deleted' => (function () use (&$deletedCount, &$partialChangesApplied): void {
                        $deletedCount++;
                        $partialChangesApplied = true;
                    })(),
                    'missing' => $missingCount++,
                    default => $skippedCount++,
                };
            }

            $summary = [
                'deleted_count' => $deletedCount,
                'skipped_count' => $skippedCount,
                'missing_count' => $missingCount,
                'file_results' => $fileResults,
            ];

            $this->executionLogger->logStepCompleted(
                $execution,
                HostSystemUpdateExecutionStep::ReplaceFiles->value,
                (string) __('dberp.system_update.file_execution.log.delete_completed', [
                    'count' => $deletedCount,
                ]),
                $summary,
                $startedAt,
            );

            $this->auditLogger->logSystemUpdateFileDeletionCompleted($execution, $package, $summary);

            return $summary;
        } catch (HostSystemUpdateFileExecutionException $exception) {
            $this->executionLogger->logStepFailed(
                $execution,
                HostSystemUpdateExecutionStep::ReplaceFiles->value,
                $exception->userFacingMessage(),
                $exception->errorCode(),
                $exception->userFacingMessage(),
                [
                    'deleted_count' => $deletedCount,
                    'file_results' => $fileResults,
                ],
                $startedAt,
            );
            $this->auditLogger->logSystemUpdateFileDeletionFailed($execution, $package, $exception);

            throw $exception;
        }
    }

    /**
     * @param  list<string>  $backedUpPaths
     * @return array{path: string, status: string}
     */
    public function deleteManifestFile(string $relativePath, array $backedUpPaths, bool $partialChangesApplied): array
    {
        $this->protectedPathGuard->assertSafeUpdateTargetPath($relativePath, 'delete_files');

        $normalized = $this->protectedPathGuard->normalizeRelativePath($relativePath);

        if ($normalized === null) {
            throw HostSystemUpdateStagingException::protectedUpdateTarget($relativePath, 'delete_files');
        }

        if (str_ends_with($normalized, '/')) {
            return ['path' => $normalized, 'status' => 'skipped_directory'];
        }

        $targetPath = $this->protectedPathGuard->projectAbsolutePath($normalized);

        if (! file_exists($targetPath)) {
            return ['path' => $normalized, 'status' => 'missing'];
        }

        if (is_dir($targetPath)) {
            return ['path' => $normalized, 'status' => 'skipped_directory'];
        }

        if (! in_array($normalized, $backedUpPaths, true)) {
            throw HostSystemUpdateFileExecutionException::deleteNotBackedUp($normalized);
        }

        if (! @unlink($targetPath)) {
            throw HostSystemUpdateFileExecutionException::deleteFailed($normalized, $partialChangesApplied);
        }

        return ['path' => $normalized, 'status' => 'deleted'];
    }
}
