package com.atlassian.bitbucket.internal.scm.git.lfs.embedded;

import com.atlassian.bitbucket.internal.scm.git.lfs.settings.GitLfsSettingsService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.Validate.notBlank;

public class LfsFileSystemHelper {

    private static final String STORE_DIRECTORY_SUFFIX = "git-lfs/storage";
    private static final Logger log = LoggerFactory.getLogger(LfsFileSystemHelper.class);
    private final long minimumFreeSpace;
    private final ApplicationPropertiesService propertiesService;
    private final Path storeDirectory;

    public LfsFileSystemHelper(ApplicationPropertiesService propertiesService, GitLfsSettingsService settingsService) {
        this.propertiesService = propertiesService;
        storeDirectory = getOrCreateStoreDirectory();
        minimumFreeSpace = settingsService.getMinimumFreeSpace();
    }

    public long getFreeSpace() {
        Path dir = getOrCreateStoreDirectory();
        try {
            long freeSpace = Files.getFileStore(dir).getUsableSpace();

            return Math.max(0, freeSpace - minimumFreeSpace);
        } catch (IOException e) {
            log.error("Failed to retrieve free space for filesystem " + dir, e);
        }

        return 0;
    }

    /**
     * Returns the base path for the hierarchy. This will be of the form:
     * {@code $BITBUCKET_HOME/shared/gitlfs/store/embedded/<hierarchyid>}
     */
    public Path getHierarchyPath(String hierarchyId) {
        notBlank(hierarchyId, "hierarchyId");
        return storeDirectory.resolve(hierarchyId);
    }

    public long getMinimumFreeSpace() {
        return minimumFreeSpace;
    }

    /**
     * Returns the fully qualified path to an object in a given hierarchy. This will be of the form:
     * {@code <hierarchy path>/<sha256[0:1]>/sha256[2:63]}
     */
    public Path getObjectPath(String hierarchyId, String oid) {
        notBlank(hierarchyId, "hierarchyId");
        notBlank(oid, "oid");
        return getHierarchyPath(hierarchyId).resolve(oid.substring(0, 2)).resolve(oid.substring(2));
    }

    /**
     * Returns the path to the base of the story directory, creating the directory and any required parents
     * if necessary.
     *
     * @throws RuntimeException is an IOException occurs. This should be considered a "server error"
     *                          and when signaling the client a HTTP status 500 is appropriate.
     */
    public Path getOrCreateStoreDirectory() {
        try {
            Path dataDir = propertiesService.getDataDir().toPath();
            if (!Files.isDirectory(dataDir)) {
                throw new RuntimeException("Data directory not found or not a directory: " + dataDir);
            }

            Path dir = dataDir.resolve(STORE_DIRECTORY_SUFFIX);
            if (!Files.isDirectory(dir)) {
                Files.createDirectories(dir);
            }

            return dir;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public boolean objectExists(@Nonnull String hierarchyId, @Nonnull String oid) {
        requireNonNull(hierarchyId, oid);
        return Files.exists(getObjectPath(hierarchyId, oid));
    }

    public boolean objectExists(@Nonnull Repository repository, @Nonnull String oid) {
        requireNonNull(repository, "repository");
        requireNonNull(oid, "oid");
        return objectExists(repository.getHierarchyId(), oid);
    }
}
