package com.atlassian.bitbucket.internal.mirroring.repositories;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.ScmFeature;
import com.atlassian.bitbucket.scm.ScmService;
import com.atlassian.bitbucket.scm.UnavailableScmException;
import com.atlassian.bitbucket.scm.UnsupportedScmException;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.bitbucket.util.FileUtils;
import com.google.common.io.Files;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.UUID;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Optional.empty;
import static java.util.Optional.of;

public class DefaultRepositoryContentHashService implements RepositoryContentHashService {
    private static final Logger log = LoggerFactory.getLogger(DefaultRepositoryContentHashService.class);

    private static final String HASH_DIR = "repo-hashes";

    private final I18nService i18nService;
    private final ApplicationPropertiesService propertiesService;
    private final ScmService scmService;

    public DefaultRepositoryContentHashService(I18nService i18nService, ApplicationPropertiesService propertiesService,
                                               ScmService scmService) {
        this.i18nService = i18nService;
        this.propertiesService = propertiesService;
        this.scmService = scmService;
    }

    @Nonnull
    public Optional<String> generateAndSetHash(Repository repository) {
        checkNotNull(repository, "repository");

        try {
            if (!scmService.isSupported(repository, ScmFeature.MIRRORS)) {
                return empty();
            }
        } catch (UnavailableScmException | UnsupportedScmException e) {
            return empty();
        }

        String hash = UUID.randomUUID().toString();
        setHash(repository, hash);

        log.debug("{} generated new content hash '{}'", repository, hash);
        return of(hash);
    }

    @Nonnull
    @Override
    public Optional<String> getHash(@Nonnull Repository repository) {
        checkNotNull(repository, "repository");

        try {
            if (!scmService.isSupported(repository, ScmFeature.MIRRORS)) {
                return empty();
            }
        } catch (UnavailableScmException | UnsupportedScmException e) {
            return empty();
        }

        try {
            // File.exists() is not reliable on NFS - just open it and handle the exception
            File contentHashFile = getHashFile(repository);
            try {
                return of(Files.toString(contentHashFile, StandardCharsets.UTF_8));
            } catch (FileNotFoundException e) {
                return generateAndSetHash(repository);
            }
        } catch (ContentHashStorageException | IOException e) {
            log.error("Unable to retrieve content hash for {}", repository, e);
            return empty();
        }
    }

    public void setHash(@Nonnull Repository repository, @Nonnull String hash) {
        checkNotNull(repository, "repository");

        if (!scmService.isSupported(repository, ScmFeature.MIRRORS)) {
            throw new IllegalArgumentException("Mirroring not supported for " + repository);
        }

        try {
            File contentHashFile = getHashFile(repository);
            File hashDir = contentHashFile.getParentFile();
            if (hashDir.mkdirs()) {
                log.debug("Created content hash directory {}", hashDir.getAbsolutePath());
            }

            File tmpFile = File.createTempFile("tmp-" + contentHashFile.getName(), null, contentHashFile.getParentFile());
            Files.write(hash, tmpFile, StandardCharsets.UTF_8);
            Files.move(tmpFile, contentHashFile);
        } catch (IOException e) {
            throw new ContentHashStorageException(
                    i18nService.createKeyedMessage("bitbucket.mirroring.contenthash.store.error", repository, e.getMessage()), e);
        }
    }

    private File getHashFile(Repository repository) {
        return FileUtils.construct(propertiesService.getDataDir(), HASH_DIR, Integer.toString(repository.getId()));
    }
}
