package p.packer.repositories;

import p.packer.messages.InitWorkspaceRequest;
import p.packer.messages.PackerProjectContext;
import p.packer.models.PackerAssetCacheEntry;
import p.packer.models.PackerRuntimeAsset;
import p.packer.models.PackerRuntimeSnapshot;
import p.packer.models.PackerWorkspaceCacheState;
import p.packer.services.PackerAssetDeclarationParser;
import p.packer.services.PackerWorkspaceFoundation;
import p.packer.PackerWorkspacePaths;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.stream.Collectors;

public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
    private final PackerWorkspaceFoundation workspaceFoundation;
    private final PackerAssetDeclarationParser parser;
    private final FileSystemPackerCacheRepository cacheRepository;
    private final PackerRuntimeAssetMaterializer assetMaterializer;

    public PackerRuntimeLoader(
            final PackerWorkspaceFoundation workspaceFoundation,
            final PackerAssetDeclarationParser parser,
            final FileSystemPackerCacheRepository cacheRepository,
            final PackerRuntimeAssetMaterializer assetMaterializer) {
        this.workspaceFoundation = Objects.requireNonNull(workspaceFoundation, "workspaceFoundation");
        this.parser = Objects.requireNonNull(parser, "parser");
        this.cacheRepository = Objects.requireNonNull(cacheRepository, "cacheRepository");
        this.assetMaterializer = Objects.requireNonNull(assetMaterializer, "assetMaterializer");
    }

    private boolean isAssetJson(Path path, BasicFileAttributes attrs) {
        return attrs.isRegularFile() && path.getFileName().toString().equals("asset.json");
    }

    @Override
    public PackerRuntimeSnapshot load(PackerProjectContext project, long generation) {
        final var safeProject = Objects.requireNonNull(project, "project");
        workspaceFoundation.initWorkspace(new InitWorkspaceRequest(safeProject));

        final var registry = workspaceFoundation.loadRegistry(safeProject);
        final PackerWorkspaceCacheState priorCacheState = loadPriorCacheState(safeProject);
        final var registryByRoot = registry
                .assets()
                .stream()
                .collect(Collectors.toMap(
                        entry -> PackerWorkspacePaths.assetRoot(safeProject, entry.root()),
                        entry -> entry));

        final List<PackerRuntimeAsset> assets = new ArrayList<>();
        final List<PackerAssetCacheEntry> refreshedCacheEntries = new ArrayList<>();
        final var assetsRoot = PackerWorkspacePaths.assetsRoot(safeProject);
        if (Files.isDirectory(assetsRoot)) {
            try (final var paths = Files.find(assetsRoot, Integer.MAX_VALUE, this::isAssetJson)) {
                final var manifests = paths
                        .map(path -> path.toAbsolutePath().normalize())
                        .sorted(Comparator.naturalOrder())
                        .toList();
                for (final var manifestPath : manifests) {
                    final var assetRoot = manifestPath.getParent();
                    final var registryEntry = Optional.ofNullable(registryByRoot.get(assetRoot));
                    final var parseResult = parser.parse(manifestPath);
                    final Optional<PackerAssetCacheEntry> priorAssetCache = registryEntry
                            .flatMap(entry -> priorCacheState.findAsset(entry.assetId()));
                    final var materialized = assetMaterializer.materialize(
                            assetRoot,
                            manifestPath,
                            registryEntry,
                            parseResult,
                            priorAssetCache);
                    assets.add(materialized.runtimeAsset());
                    materialized.assetCacheEntry().ifPresent(refreshedCacheEntries::add);
                }
            } catch (IOException exception) {
                throw new p.packer.exceptions.PackerRegistryException(
                        "Unable to build runtime snapshot for " + safeProject.projectId(),
                        exception);
            }
        }

        final PackerWorkspaceCacheState refreshedCacheState = new PackerWorkspaceCacheState(
                PackerWorkspaceCacheState.CURRENT_SCHEMA_VERSION,
                refreshedCacheEntries.stream()
                        .sorted(Comparator.comparingInt(PackerAssetCacheEntry::assetId))
                        .toList());
        cacheRepository.save(safeProject, refreshedCacheState);
        return new PackerRuntimeSnapshot(generation, registry, assets, refreshedCacheState);
    }

    private PackerWorkspaceCacheState loadPriorCacheState(PackerProjectContext project) {
        try {
            return cacheRepository.load(project);
        } catch (p.packer.exceptions.PackerRegistryException exception) {
            return PackerWorkspaceCacheState.EMPTY;
        }
    }
}
