package com.atlassian.stash.internal.scm.git.binary;

import com.atlassian.fugue.Option;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.stash.internal.scm.git.AbstractGitTest;
import com.atlassian.stash.internal.scm.git.DefaultGitScmConfig;
import com.atlassian.bitbucket.util.Version;
import com.atlassian.bitbucket.scm.git.GitNotFoundException;
import com.atlassian.bitbucket.scm.git.GitUnsupportedVersionException;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.File;
import java.util.Arrays;

import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.*;

/**
 * Augments the {@code DefaultGitBinaryHelperTest} in {@code bitbucket-git-binary} by running similar tests
 * through the {@link DefaultGitScmConfig} to verify the <i>visible</i> behavior.
 */
public class DefaultGitBinaryHelperTest extends AbstractGitTest {

    @Rule
    public final TemporaryFolder folder = new TemporaryFolder();

    @Autowired
    private DefaultGitBinaryHelper binaryHelper;
    @Autowired
    private I18nService i18nService;
    @Autowired
    private ApplicationPropertiesService propertiesService;

    @Before
    public void setup() {
        binaryHelper = spy(binaryHelper);
    }

    @Test
    public void testGetBinary() {
        DefaultGitScmConfig config = new DefaultGitScmConfig(propertiesService, i18nService, binaryHelper);
        //Verifies an exception is not thrown, ensuring the Git version being tested is supported
        String actualPath = config.getBinary();

        //This verifies that, when a version of Git is configured via system property, as it is in the Git matrix
        //builds, that version is _used_. There have been multiple regressions, in the past, where other changes
        //have caused the matrix to ignore the configured version, effectively negating its benefits by causing
        //every stage/task to verify the build agent's installed version of Git, not the matrix-specified version.
        String expectedPath = config.getProperty(DefaultGitScmConfig.PROP_BINARY_PATH);
        if (expectedPath == null || StringUtils.isBlank(expectedPath)) {
            //There are situations in which the configured Git isn't actually passed via Surefire to the tests,
            //so to try and help detect that we log the Git we're using
            LoggerFactory.getLogger(getClass()).warn("Using auto-detected Git at {}", actualPath);
        } else {
            //Normalize away slash directions and other bits of trivia
            expectedPath = new File(expectedPath).getAbsolutePath();
            actualPath = new File(actualPath).getAbsolutePath();
            LoggerFactory.getLogger(getClass()).warn("Using Git at {} (Configured to use: {})",
                    actualPath, expectedPath);

            assertEquals("The configured Git binary, " + expectedPath + ", is not being used: " + actualPath,
                    expectedPath, actualPath);
        }
    }

    @Test(expected = GitNotFoundException.class)
    public void testGetBinaryWhenNotFound() {
        hideConfiguredGit();

        doReturn(Arrays.asList("/nonexistent/path", folder.getRoot().getAbsolutePath()))
                .when(binaryHelper).getSearchPaths();

        new DefaultGitScmConfig(propertiesService, i18nService, binaryHelper).getBinary();
    }

    @Test(expected = GitUnsupportedVersionException.class)
    public void testGetBinaryWith1_8_4_3() throws Exception {
        runVersionTest(GitBinaryHelper.VERSION_1_8_4_3);
    }

    @Test(expected = GitUnsupportedVersionException.class)
    public void testGetBinaryWith2_0_2() throws Exception {
        runVersionTest(GitBinaryHelper.VERSION_2_0_2);
    }

    @Test(expected = GitUnsupportedVersionException.class)
    public void testGetBinaryWith2_0_3() throws Exception {
        runVersionTest(GitBinaryHelper.VERSION_2_0_3);
    }

    private String runVersionTest(Version version) throws Exception {
        hideConfiguredGit();

        //This "binary" is our placeholder to make the path scanning in DefaultGitBinaryHelper "find" git
        File binary = folder.newFile("git");
        assumeTrue(binary.setExecutable(true));

        doReturn(Arrays.asList("/nonexistent/path", binary.getParent())).when(binaryHelper).getSearchPaths();
        doReturn(false).when(binaryHelper).isWindows(); //Act like Linux on every platform; it simplifies the test
        doReturn(Option.some(version)).when(binaryHelper).versionOf(eq(binary));

        return new DefaultGitScmConfig(propertiesService, i18nService, binaryHelper).getBinary();
    }

    /**
     * Hides the system property used to specify the location of the Git binary when running the Git matrix builds.
     * Otherwise, the presence of that binary causes tests like {@link #testGetBinaryWhenNotFound()}, where mocking
     * is used to spoof the existence of a specific version, to fail.
     */
    private void hideConfiguredGit() {
        //Replace the ApplicationPropertiesService with a spy and intercept efforts to retrieve the configured
        //Git binary to always return null
        propertiesService = spy(propertiesService);

        doReturn(null).when(propertiesService).getPluginProperty(endsWith(DefaultGitScmConfig.PROP_BINARY_PATH));
    }
}
