package p.packer.services;

import p.packer.PackerWorkspacePaths;
import p.packer.messages.*;
import p.packer.messages.assets.AssetAction;
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
import p.packer.models.*;
import p.packer.repositories.PackerRuntimeRegistry;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public final class PackerAssetActionReadService {
    private final PackerRuntimeRegistry runtimeRegistry;
    private final PackerAssetReferenceResolver assetReferenceResolver;
    private final PackerRegistryLookup registryLookup;

    public PackerAssetActionReadService(
            PackerRuntimeRegistry runtimeRegistry,
            PackerAssetReferenceResolver assetReferenceResolver,
            PackerRegistryLookup registryLookup) {
        this.runtimeRegistry = Objects.requireNonNull(runtimeRegistry, "runtimeRegistry");
        this.assetReferenceResolver = Objects.requireNonNull(assetReferenceResolver, "assetReferenceResolver");
        this.registryLookup = Objects.requireNonNull(registryLookup, "registryLookup");
    }

    public GetAssetActionsResult getAssetActions(GetAssetActionsRequest request) {
        final PackerProjectContext project = Objects.requireNonNull(request, "request").project();
        final PackerRuntimeSnapshot snapshot = runtimeRegistry.getOrLoad(project).snapshot();
        final PackerRegisterAssetEvaluation registerEvaluation = evaluateRegister(snapshot, project, request.assetReference());
        final PackerMoveAssetEvaluation moveEvaluation = evaluateMove(snapshot, project, request.assetReference(), null);
        final PackerDeleteAssetEvaluation deleteEvaluation = evaluateDelete(snapshot, project, request.assetReference());
        final List<PackerAssetActionAvailability> actions = new ArrayList<>();
        if (registerEvaluation.resolved().registryEntry().isEmpty()) {
            actions.add(new PackerAssetActionAvailability(
                    AssetAction.REGISTER,
                    registerEvaluation.canRegister(),
                    true,
                    registerEvaluation.reason()));
        }
        if (moveEvaluation.resolved().runtimeAsset().isPresent()) {
            actions.add(new PackerAssetActionAvailability(
                    AssetAction.MOVE,
                    moveEvaluation.canMove(),
                    true,
                    moveEvaluation.reason()));
        }
        if (deleteEvaluation.resolved().runtimeAsset().isPresent()) {
            actions.add(new PackerAssetActionAvailability(
                    AssetAction.DELETE,
                    deleteEvaluation.canDelete(),
                    true,
                    deleteEvaluation.reason()));
        }

        final PackerOperationStatus status;
        if (registerEvaluation.resolved().runtimeAsset().isEmpty()
                && moveEvaluation.resolved().runtimeAsset().isEmpty()
                && deleteEvaluation.resolved().runtimeAsset().isEmpty()) {
            status = PackerOperationStatus.FAILED;
        } else if (combinedDiagnostics(registerEvaluation.diagnostics(), moveEvaluation.diagnostics(), deleteEvaluation.diagnostics())
                .stream().anyMatch(PackerDiagnostic::blocking)) {
            status = PackerOperationStatus.PARTIAL;
        } else {
            status = PackerOperationStatus.SUCCESS;
        }
        return new GetAssetActionsResult(
                status,
                "Asset action capabilities resolved from runtime snapshot.",
                PackerReadMessageMapper.toAssetActionAvailabilityDTOs(actions),
                PackerReadMessageMapper.toDiagnosticDTOs(combinedDiagnostics(
                        registerEvaluation.diagnostics(),
                        moveEvaluation.diagnostics(),
                        deleteEvaluation.diagnostics())));
    }

    public PackerRegisterAssetEvaluation evaluateRegister(
            PackerRuntimeSnapshot snapshot,
            PackerProjectContext project,
            AssetReference assetReference) {
        final PackerResolvedAssetReference resolved = assetReferenceResolver.resolve(project, snapshot, assetReference);
        final List<PackerDiagnostic> diagnostics = new ArrayList<>(resolved.diagnostics());
        if (resolved.registryEntry().isPresent()) {
            return new PackerRegisterAssetEvaluation(resolved, diagnostics, false, "Asset is already registered.");
        }
        if (resolved.runtimeAsset().isEmpty()) {
            diagnostics.add(new PackerDiagnostic(
                    PackerDiagnosticSeverity.ERROR,
                    PackerDiagnosticCategory.STRUCTURAL,
                    "asset.json was not found for the requested asset root.",
                    resolved.assetRoot().resolve("asset.json"),
                    true));
            return new PackerRegisterAssetEvaluation(resolved, diagnostics, false, "asset.json was not found for the requested asset root.");
        }

        final PackerRuntimeAsset runtimeAsset = resolved.runtimeAsset().get();
        final PackerAssetDeclarationParseResult parsed = runtimeAsset.parsedDeclaration();
        diagnostics.addAll(parsed.diagnostics());
        if (!parsed.valid()) {
            return new PackerRegisterAssetEvaluation(resolved, diagnostics, false, firstBlockingReason(diagnostics, "Asset declaration is invalid."));
        }

        final PackerAssetDeclaration declaration = parsed.declaration();
        final Optional<PackerRegistryEntry> sameUuidEntry = registryLookup.findByAssetUuid(snapshot.registry(), declaration.assetUuid());
        if (sameUuidEntry.isPresent()) {
            final Path registeredRoot = PackerWorkspacePaths.assetRoot(project, sameUuidEntry.get().root());
            if (!registeredRoot.equals(resolved.assetRoot())) {
                diagnostics.add(new PackerDiagnostic(
                        PackerDiagnosticSeverity.ERROR,
                        PackerDiagnosticCategory.STRUCTURAL,
                        "Field 'asset_uuid' is already registered to another asset root.",
                        runtimeAsset.manifestPath(),
                        true));
                return new PackerRegisterAssetEvaluation(
                        resolved,
                        diagnostics,
                        false,
                        "Asset UUID is already registered by another asset.");
            }
        }

        final Optional<PackerRegistryEntry> sameRootEntry = registryLookup.findByRoot(project, snapshot.registry(), resolved.assetRoot());
        if (sameRootEntry.isPresent()) {
            return new PackerRegisterAssetEvaluation(resolved, diagnostics, false, "Asset root is already registered.");
        }

        return new PackerRegisterAssetEvaluation(resolved, diagnostics, true, null);
    }

    public PackerDeleteAssetEvaluation evaluateDelete(
            PackerRuntimeSnapshot snapshot,
            PackerProjectContext project,
            AssetReference assetReference) {
        final PackerResolvedAssetReference resolved = assetReferenceResolver.resolve(project, snapshot, assetReference);
        final List<PackerDiagnostic> diagnostics = new ArrayList<>(resolved.diagnostics());
        if (resolved.runtimeAsset().isEmpty()) {
            if (resolved.registryEntry().isPresent()) {
                diagnostics.add(new PackerDiagnostic(
                        PackerDiagnosticSeverity.ERROR,
                        PackerDiagnosticCategory.STRUCTURAL,
                        "asset.json was not found for the requested asset root.",
                        resolved.assetRoot().resolve("asset.json"),
                        true));
            }
            return new PackerDeleteAssetEvaluation(
                    resolved,
                    diagnostics,
                    false,
                    "asset.json was not found for the requested asset root.");
        }
        return new PackerDeleteAssetEvaluation(resolved, diagnostics, true, null);
    }

    public PackerMoveAssetEvaluation evaluateMove(
            PackerRuntimeSnapshot snapshot,
            PackerProjectContext project,
            AssetReference assetReference,
            String requestedTargetRoot) {
        final PackerResolvedAssetReference resolved = assetReferenceResolver.resolve(project, snapshot, assetReference);
        final List<PackerDiagnostic> diagnostics = new ArrayList<>(resolved.diagnostics());
        if (resolved.runtimeAsset().isEmpty()) {
            diagnostics.add(new PackerDiagnostic(
                    PackerDiagnosticSeverity.ERROR,
                    PackerDiagnosticCategory.STRUCTURAL,
                    "asset.json was not found for the requested asset root.",
                    resolved.assetRoot().resolve("asset.json"),
                    true));
            return new PackerMoveAssetEvaluation(
                    resolved,
                    diagnostics,
                    false,
                    "asset.json was not found for the requested asset root.",
                    Optional.empty(),
                    Optional.empty(),
                    Optional.empty());
        }

        if (requestedTargetRoot == null) {
            return new PackerMoveAssetEvaluation(
                    resolved,
                    diagnostics,
                    true,
                    null,
                    Optional.empty(),
                    Optional.empty(),
                    Optional.empty());
        }

        final String normalizedTargetRoot = normalizeRelativeAssetRoot(requestedTargetRoot);
        if (normalizedTargetRoot == null) {
            return new PackerMoveAssetEvaluation(
                    resolved,
                    diagnostics,
                    false,
                    "Target root must stay inside assets/ and use a non-blank relative path.",
                    Optional.empty(),
                    Optional.empty(),
                    Optional.empty());
        }

        final Path targetRoot = PackerWorkspacePaths.assetRoot(project, normalizedTargetRoot);
        if (!targetRoot.startsWith(PackerWorkspacePaths.assetsRoot(project))) {
            return new PackerMoveAssetEvaluation(
                    resolved,
                    diagnostics,
                    false,
                    "Target root must stay inside assets/.",
                    Optional.of(normalizedTargetRoot),
                    Optional.of(targetRoot),
                    Optional.of(targetRoot.resolve("asset.json")));
        }

        if (targetRoot.equals(resolved.assetRoot())) {
            return new PackerMoveAssetEvaluation(
                    resolved,
                    diagnostics,
                    false,
                    "Target root must differ from the current asset root.",
                    Optional.of(normalizedTargetRoot),
                    Optional.of(targetRoot),
                    Optional.of(targetRoot.resolve("asset.json")));
        }

        final Path targetManifestPath = targetRoot.resolve("asset.json");
        if (java.nio.file.Files.isRegularFile(targetManifestPath)) {
            return new PackerMoveAssetEvaluation(
                    resolved,
                    diagnostics,
                    false,
                    "Target root already contains asset.json.",
                    Optional.of(normalizedTargetRoot),
                    Optional.of(targetRoot),
                    Optional.of(targetManifestPath));
        }

        return new PackerMoveAssetEvaluation(
                resolved,
                diagnostics,
                true,
                null,
                Optional.of(normalizedTargetRoot),
                Optional.of(targetRoot),
                Optional.of(targetManifestPath));
    }

    private String firstBlockingReason(List<PackerDiagnostic> diagnostics, String fallback) {
        return diagnostics.stream()
                .filter(PackerDiagnostic::blocking)
                .map(PackerDiagnostic::message)
                .filter(message -> message != null && !message.isBlank())
                .findFirst()
                .orElse(fallback);
    }

    @SafeVarargs
    private List<PackerDiagnostic> combinedDiagnostics(List<PackerDiagnostic>... groups) {
        final List<PackerDiagnostic> combined = new ArrayList<>();
        for (List<PackerDiagnostic> group : groups) {
            combined.addAll(group);
        }
        return combined;
    }

    private String normalizeRelativeAssetRoot(String candidate) {
        final String raw = Objects.requireNonNullElse(candidate, "").trim().replace('\\', '/');
        if (raw.isBlank()) {
            return null;
        }
        final Path normalized = Path.of(raw).normalize();
        if (normalized.isAbsolute() || normalized.startsWith("..")) {
            return null;
        }
        final String value = normalized.toString().replace('\\', '/');
        return value.isBlank() ? null : value;
    }
}
