package io.cameleer.server.app.storage;

import io.cameleer.server.core.storage.ArtifactCoordinates;
import io.cameleer.server.core.storage.ArtifactStore;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

public class FilesystemArtifactStore implements ArtifactStore {

    private final Path root;

    public FilesystemArtifactStore(String root) {
        this.root = Path.of(root);
    }

    private Path pathOf(ArtifactCoordinates coords) {
        return root.resolve(coords.filesystemKey());
    }

    @Override
    public String put(ArtifactCoordinates coords, InputStream bytes, long size) throws IOException {
        Path target = pathOf(coords);
        Files.createDirectories(target.getParent());
        Path tmp = target.resolveSibling(target.getFileName() + ".tmp");
        try (InputStream in = bytes) {
            Files.copy(in, tmp, StandardCopyOption.REPLACE_EXISTING);
            Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
        } catch (IOException | RuntimeException e) {
            try { Files.deleteIfExists(tmp); }
            catch (IOException suppress) { e.addSuppressed(suppress); }
            throw e;
        }
        return target.toString();
    }

    @Override
    public InputStream get(ArtifactCoordinates coords) throws IOException {
        return Files.newInputStream(pathOf(coords));
    }

    @Override
    public long size(ArtifactCoordinates coords) throws IOException {
        return Files.size(pathOf(coords));
    }

    @Override
    public boolean exists(ArtifactCoordinates coords) {
        return Files.exists(pathOf(coords));
    }

    @Override
    public void delete(ArtifactCoordinates coords) throws IOException {
        Path target = pathOf(coords);
        Files.deleteIfExists(target);
        // Sweep empty {appId}/v{n}/ then {appId}/ if now empty.
        // A concurrent put of a sibling version can create v{n+1}/ mid-sweep —
        // DirectoryNotEmptyException just means "stop sweeping," which is what
        // "remove empty parent dirs" semantically means.
        Path versionDir = target.getParent();
        if (versionDir != null && Files.isDirectory(versionDir) && isEmpty(versionDir)) {
            try { Files.delete(versionDir); }
            catch (DirectoryNotEmptyException e) { return; }
            Path appDir = versionDir.getParent();
            if (appDir != null && Files.isDirectory(appDir) && isEmpty(appDir)) {
                try { Files.delete(appDir); }
                catch (DirectoryNotEmptyException e) { /* leave it */ }
            }
        }
    }

    @Override
    public String locator(ArtifactCoordinates coords) {
        return pathOf(coords).toString();
    }

    private static boolean isEmpty(Path dir) throws IOException {
        try (var entries = Files.list(dir)) {
            return entries.findFirst().isEmpty();
        }
    }
}
