package com.atlassian.stash.internal.repository.sync;

import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.git.GitScmConfig;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;

import javax.annotation.Nonnull;
import java.util.concurrent.TimeUnit;

/**
 * @since 2.6
 */
public class DefaultRefSyncConfig implements RefSyncConfig {

    public static final int DEFAULT_COMMAND_SIZE = 32000; //Limit based on Windows; Linux's is generally much higher
    public static final long DEFAULT_FETCH_TIMEOUT = TimeUnit.MINUTES.toSeconds(5);
    public static final long DEFAULT_MERGE_TIMEOUT = TimeUnit.MINUTES.toSeconds(5);
    public static final long DEFAULT_SYNC_DELAY = 30L;
    public static final int DEFAULT_THREADS = 3;
    public static final int MINIMUM_COMMAND_SIZE = 4000;
    public static final long MINIMUM_FETCH_TIMEOUT = TimeUnit.MINUTES.toSeconds(2);
    public static final long MINIMUM_MERGE_TIMEOUT = TimeUnit.MINUTES.toSeconds(2);
    public static final String PROP_PREFIX = "plugin.bitbucket-repository-ref-sync.";
    /**
     * Spring only does recursive property replacement for placeholders in the host application, so the only way to
     * reuse the value configured for the Git SCM is to load it directly. This property is retrieved using
     * {@link GitScmConfig#getProperty(String, int)}, so the {@link #PROP_PREFIX prefix} is not applied here. Should
     * a day come where we want the two to be different, separate configuration can be added at that time.
     *
     * @since 3.1
     */
    public static final String PROP_ENVIRONMENT_COMMAND_SIZE = "environment.commandsize";
    public static final String PROP_FETCH_TIMEOUT = PROP_PREFIX + "fetch.timeout";
    public static final String PROP_MERGE_TIMEOUT = PROP_PREFIX + "merge.timeout";
    public static final String PROP_SYNC_DELAY = PROP_PREFIX + "sync.delay";
    public static final String PROP_THREADS = PROP_PREFIX + "threads";
    public static final String SCM_PROP_USE_ALTERNATES = "forks.usealternates";

    private final GitScmConfig config;
    private final long fetchTimeout;
    private final long mergeTimeout;
    private final boolean possible;
    private final long syncDelay;
    private final int threads;

    public DefaultRefSyncConfig(GitScmConfig config, ApplicationPropertiesService propertiesService) {
        this.config = config;

        fetchTimeout = Math.max(
                propertiesService.getPluginProperty(PROP_FETCH_TIMEOUT, DEFAULT_FETCH_TIMEOUT),
                MINIMUM_FETCH_TIMEOUT);
        mergeTimeout = Math.max(
                propertiesService.getPluginProperty(PROP_MERGE_TIMEOUT, DEFAULT_MERGE_TIMEOUT),
                MINIMUM_MERGE_TIMEOUT);
        syncDelay = Math.max(propertiesService.getPluginProperty(PROP_SYNC_DELAY, DEFAULT_SYNC_DELAY), 0);
        threads = Math.max(propertiesService.getPluginProperty(PROP_THREADS, DEFAULT_THREADS), 1);

        //This is a pretty nasty cross-domain trespass, but synchronization will not work correctly without alternates.
        //Specifically, the update-ref operations used to synchronize tags rely 100% on the existence of alternates to
        //provide access to any object that is tagged upstream without first requiring a fetch.
        //
        //We don't want to raise the visibility of the option to disable alternates, so it can't easily be called out
        //in application-default.properties. It could be configured as a separate flag, but that makes it harder to
        //link the state to ensure consistency. Directly accessing the git plugin's setting is the simplest approach
        //to achieve the desired results.
        possible = config.getProperty(SCM_PROP_USE_ALTERNATES, true);
    }

    @Override
    public int getCommandSize() {
        return Math.max(config.getProperty(PROP_ENVIRONMENT_COMMAND_SIZE, DEFAULT_COMMAND_SIZE), MINIMUM_COMMAND_SIZE);
    }

    @Override
    public long getFetchTimeout() {
        return fetchTimeout;
    }

    @Override
    public long getMergeTimeout() {
        return mergeTimeout;
    }

    @Nonnull
    @Override
    public String getRelativePath(@Nonnull Repository repository, @Nonnull Repository relativeTo) {
        return config.getRelativePath(repository, relativeTo);
    }

    @Override
    public long getSyncDelay() {
        return syncDelay;
    }

    @Override
    public int getThreadCount() {
        return threads;
    }

    @Override
    public boolean isPossible() {
        return possible;
    }
}
