package p.packer.repositories;

import p.packer.models.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

public final class PackerRuntimeAssetMaterializer {
    private final PackerAssetWalker assetWalker;

    public PackerRuntimeAssetMaterializer(PackerAssetWalker assetWalker) {
        this.assetWalker = Objects.requireNonNull(assetWalker, "assetWalker");
    }

    public PackerRuntimeAssetMaterialization materialize(
            Path assetRoot,
            Path manifestPath,
            Optional<PackerRegistryEntry> registryEntry,
            PackerAssetDeclarationParseResult parseResult,
            Optional<PackerAssetCacheEntry> priorAssetCache) {
        final Path normalizedAssetRoot = Objects.requireNonNull(assetRoot, "assetRoot").toAbsolutePath().normalize();
        final Path normalizedManifestPath = Objects.requireNonNull(manifestPath, "manifestPath").toAbsolutePath().normalize();
        final Optional<PackerRegistryEntry> safeRegistryEntry = Objects.requireNonNull(registryEntry, "registryEntry");
        final PackerAssetDeclarationParseResult safeParseResult = Objects.requireNonNull(parseResult, "parseResult");
        final Optional<PackerAssetCacheEntry> safePriorAssetCache = Objects.requireNonNull(priorAssetCache, "priorAssetCache");

        if (safeRegistryEntry.isEmpty() || !safeParseResult.valid()) {
            return new PackerRuntimeAssetMaterialization(
                    new PackerRuntimeAsset(
                            normalizedAssetRoot,
                            normalizedManifestPath,
                            safeRegistryEntry,
                            safeParseResult,
                            PackerRuntimeWalkProjection.EMPTY,
                            List.of()),
                    Optional.empty());
        }

        final List<String> availableFiles = listAvailableFiles(normalizedAssetRoot);
        final PackerWalkResult walkResult = assetWalker.walk(
                normalizedAssetRoot,
                safeParseResult.declaration(),
                safePriorAssetCache);
        final List<PackerRuntimeWalkFile> buildCandidateFiles = walkResult.probeResults().stream()
                .map(probeResult -> toRuntimeWalkFile(normalizedAssetRoot, probeResult))
                .sorted(Comparator.comparing(PackerRuntimeWalkFile::relativePath, String.CASE_INSENSITIVE_ORDER))
                .toList();
        final long measuredBankSizeBytes = buildCandidateFiles.stream()
                .mapToLong(PackerRuntimeWalkFile::size)
                .sum();
        final PackerRuntimeWalkProjection walkProjection = new PackerRuntimeWalkProjection(
                availableFiles,
                buildCandidateFiles,
                measuredBankSizeBytes);
        final PackerRuntimeAsset runtimeAsset = new PackerRuntimeAsset(
                normalizedAssetRoot,
                normalizedManifestPath,
                safeRegistryEntry,
                safeParseResult,
                walkProjection,
                walkResult.diagnostics());
        final PackerAssetCacheEntry assetCacheEntry = new PackerAssetCacheEntry(
                safeRegistryEntry.get().assetId(),
                buildCandidateFiles.stream()
                        .map(file -> new PackerFileCacheEntry(
                                file.relativePath(),
                                file.mimeType(),
                                file.size(),
                                file.lastModified(),
                                file.fingerprint(),
                                file.metadata()))
                        .toList());
        return new PackerRuntimeAssetMaterialization(runtimeAsset, Optional.of(assetCacheEntry));
    }

    private List<String> listAvailableFiles(Path assetRoot) {
        try (var paths = Files.list(assetRoot)
                .filter(Files::isRegularFile)
                .filter(path -> !path.getFileName().toString().equalsIgnoreCase("asset.json"))
                .map(path -> assetRoot.relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/'))
                .sorted(String.CASE_INSENSITIVE_ORDER)) {
            return paths.toList();
        } catch (IOException exception) {
            return List.of();
        }
    }

    private PackerRuntimeWalkFile toRuntimeWalkFile(Path assetRoot, PackerProbeResult probeResult) {
        final PackerFileProbe fileProbe = probeResult.fileProbe();
        return new PackerRuntimeWalkFile(
                assetRoot.relativize(fileProbe.path().toAbsolutePath().normalize()).toString().replace('\\', '/'),
                fileProbe.mimeType(),
                fileProbe.size(),
                fileProbe.lastModified(),
                PackerFileFingerprint.sha256(fileProbe),
                probeResult.metadata(),
                probeResult.diagnostics());
    }

    public record PackerRuntimeAssetMaterialization(
            PackerRuntimeAsset runtimeAsset,
            Optional<PackerAssetCacheEntry> assetCacheEntry) {
        public PackerRuntimeAssetMaterialization {
            runtimeAsset = Objects.requireNonNull(runtimeAsset, "runtimeAsset");
            assetCacheEntry = Objects.requireNonNull(assetCacheEntry, "assetCacheEntry");
        }
    }
}
