package p.packer.services;

import p.packer.dtos.PackerAssetActionAvailabilityDTO;
import p.packer.messages.AssetReference;
import p.packer.messages.GetAssetActionsRequest;
import p.packer.messages.GetAssetActionsResult;
import p.packer.messages.PackerOperationStatus;
import p.packer.messages.PackerProjectContext;
import p.packer.messages.assets.AssetAction;
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
import p.packer.models.PackerAssetActionAvailability;
import p.packer.models.PackerAssetDeclaration;
import p.packer.models.PackerAssetDeclarationParseResult;
import p.packer.models.PackerDiagnostic;
import p.packer.models.PackerRegisterAssetEvaluation;
import p.packer.models.PackerRegistryEntry;
import p.packer.models.PackerResolvedAssetReference;
import p.packer.models.PackerRuntimeAsset;
import p.packer.models.PackerRuntimeSnapshot;

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 List<PackerAssetActionAvailability> actions = new ArrayList<>();
        if (registerEvaluation.resolved().registryEntry().isEmpty()) {
            actions.add(new PackerAssetActionAvailability(
                    AssetAction.REGISTER,
                    registerEvaluation.canRegister(),
                    true,
                    registerEvaluation.reason()));
        }

        final PackerOperationStatus status;
        if (registerEvaluation.resolved().runtimeAsset().isEmpty()) {
            status = PackerOperationStatus.FAILED;
        } else if (registerEvaluation.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(registerEvaluation.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);
    }

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