package com.atlassian.stash.internal.scm.git.command.clone;

import com.atlassian.bitbucket.scm.git.command.clone.GitClone;
import com.atlassian.bitbucket.scm.annotation.Repo;
import com.atlassian.bitbucket.scm.CommandFailedException;
import com.atlassian.stash.internal.scm.git.AbstractGitTest;
import com.atlassian.stash.internal.scm.git.util.ConfigurableGitScmConfig;
import com.atlassian.bitbucket.repository.Branch;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.git.GitAgent;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.File;

import static com.atlassian.stash.internal.scm.git.GitFactoriesTestHelper.StringHandler;
import static com.atlassian.stash.internal.scm.git.GitFactoriesTestHelper.getHead;
import static com.atlassian.stash.internal.scm.git.GitFactoriesTestHelper.getOrigin;
import static com.atlassian.stash.internal.scm.git.GitFactoriesTestHelper.isBareRepository;
import static com.atlassian.stash.internal.scm.git.GitFactoriesTestHelper.isSharedRepository;
import static com.atlassian.stash.internal.scm.git.GitFactoriesTestHelper.hasCheckout;
import static org.hamcrest.Matchers.isEmptyString;

/**
 * Tests various permutations of the {@link GitClone} and its associated builder.
 */
@SuppressWarnings("ConstantConditions")
public class CloneCommandTest extends AbstractGitTest {

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    @Autowired
    private GitAgent agent;
    @Autowired
    private GitCommandBuilderFactory builderFactory;
    @Mock
    private Repository clone;
    private File cloneDir;
    @Autowired
    private ConfigurableGitScmConfig config;
    @Repo
    private Repository repository;

    @Before
    public void setUp() {
        cloneDir = temporaryFolder.getRoot();

        config.setRepositoryDir(clone, cloneDir);
    }

    @Test
    public void testClone() throws Exception {
        builderFactory.builder().clone().normal()
                .origin(config.getRepositoryDir(repository))
                .directory(cloneDir).build().call();
        assertExpectedClone(repository, clone, false, false, true);
    }

    @Test
    public void testCloneAsShared() throws Exception {
        builderFactory.builder().clone().normal()
                .origin(config.getRepositoryDir(repository))
                .directory(cloneDir).shared(true).build().call();
        assertExpectedClone(repository, clone, false, true, true);
    }

    @Test
    public void testCloneWithBranch() throws Exception {
        Branch branch = agent.resolveBranch(repository, "basic_branching");
        builderFactory.builder().clone().normal()
                .origin(config.getRepositoryDir(repository))
                .directory(cloneDir).branch(branch.getDisplayId()).build().call();
        assertExpectedClone(repository, clone, false, false, true, branch.getId());
    }

    @Test
    public void testCloneWithoutCheckout() throws Exception {
        builderFactory.builder().clone().normal()
                .origin(config.getRepositoryDir(repository))
                .directory(cloneDir).checkout(false).build().call();
        assertExpectedClone(repository, clone, false, false, false);
    }

    @Test
    public void testCloneQuietly() throws Exception {
        StringHandler errorHandler = new StringHandler();
        StringHandler outputHandler = new StringHandler();
        builderFactory.builder().errorHandler(errorHandler).clone().bare()
                .origin(config.getRepositoryDir(repository))
                .directory(cloneDir).quiet(true).build(outputHandler).call();

        assertExpectedClone(repository, clone, true, false, false);
        assertThat(outputHandler.getOutput(), isEmptyString());
        assertThat(errorHandler.getOutput(), isEmptyString());
    }

    @Test
    public void testCloneAsMirror() throws Exception {
        builderFactory.builder().clone().mirror()
                .origin(config.getRepositoryDir(repository))
                .directory(cloneDir).build().call();
        assertExpectedClone(repository, clone, true, false, false);
    }

    @Test
    public void testCreateBareClone() throws Exception {
        builderFactory.builder().clone().bare()
                .origin(config.getRepositoryDir(repository))
                .directory(cloneDir).build().call();
        assertExpectedClone(repository, clone, true, false, false);
    }

    @Test
    public void testCreateBareSharedClone() throws Exception {
        builderFactory.builder().clone().bare()
                .origin(config.getRepositoryDir(repository))
                .directory(cloneDir).shared(true).build().call();
        assertExpectedClone(repository, clone, true, true, false);
    }

    @Test
    public void testCreateBareCloneWithBranch() throws Exception {
        Branch branch = agent.resolveBranch(repository, "basic_branching");
        builderFactory.builder().clone().bare()
                .origin(config.getRepositoryDir(repository))
                .directory(cloneDir).branch(branch.getDisplayId()).build().call();
        assertExpectedClone(repository, clone, true, false, false, branch.getId());
    }

    @Test(expected = IllegalStateException.class)
    public void testCanNotCreateBareCloneWithCheckout() throws Exception {
        builderFactory.builder().clone().bare()
                .origin(config.getRepositoryDir(repository))
                .directory(cloneDir).checkout(true).build().call();
    }

    @Test(expected = CommandFailedException.class)
    public void testCanNotCloneMissingRepo() throws Exception {
        File source = temporaryFolder.newFolder();
        assertTrue(source.delete());
        File target = temporaryFolder.newFolder();

        builderFactory.builder().clone().normal()
                .origin(source)
                .directory(target).build().call();
    }

    @Test(expected = CommandFailedException.class)
    public void testCanNotCloneOnAnNonEmptyDir() throws Exception {
        temporaryFolder.newFile();
        builderFactory.builder().clone().normal()
                .origin(config.getRepositoryDir(repository))
                .directory(temporaryFolder.getRoot()).build().call();
    }

    private void assertExpectedClone(Repository from, Repository to, boolean bare, boolean shared, boolean hasCheckout) {
        assertExpectedClone(from, to, bare, shared, hasCheckout, agent.getHead(repository));
    }

    private void assertExpectedClone(Repository from, Repository to, boolean bare, boolean shared, boolean hasCheckout, String cloneHead) {
        File cloneDir = config.getRepositoryDir(to);
        assertEquals("should " + (bare ? "" : "not ") + "be a bare from; ", bare, isBareRepository(cloneDir));
        assertEquals("should " + (shared ? "" : "not ") + "be a shared from; ", shared, isSharedRepository(cloneDir));
        assertEquals("should" + (hasCheckout ? "" : "not ") + "have any checked out content", hasCheckout, hasCheckout(cloneDir));
        assertEquals(config.getRepositoryDir(from).getAbsolutePath(), new File(getOrigin(cloneDir, builderFactory)).getAbsolutePath());
        assertEquals(cloneHead, getHead(cloneDir));
    }

}
