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

import com.atlassian.bitbucket.scm.annotation.Repo;
import com.atlassian.stash.internal.scm.git.AbstractGitTest;
import com.atlassian.stash.internal.scm.git.DefaultGitStatus;
import com.atlassian.stash.internal.scm.git.GitFileStatus;
import com.atlassian.stash.internal.scm.git.GitStatus;
import com.atlassian.stash.internal.scm.git.command.status.CollectingStatusCallback;
import com.atlassian.stash.internal.scm.git.command.status.StatusOutputHandler;
import com.atlassian.stash.internal.scm.git.util.ConfigurableGitScmConfig;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.reset.GitResetBuilder;
import com.atlassian.bitbucket.util.PageRequestImpl;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
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 java.io.IOException;
import java.util.Arrays;
import java.util.List;

import static com.atlassian.stash.internal.scm.git.GitFactoriesTestHelper.StringHandler;
import static org.hamcrest.Matchers.*;

public class ResetCommandTest extends AbstractGitTest {

    @Autowired
    private ConfigurableGitScmConfig config;
    @Autowired
    private GitCommandBuilderFactory builderFactory;
    @Repo
    private Repository origin;

    @Mock
    private Repository repository;
    @Rule
    public TemporaryFolder tempFolder = new TemporaryFolder();

    @Before
    public void setUp() throws Exception {
        File dir = tempFolder.getRoot();
        builderFactory.builder().clone().normal().origin(config.getRepositoryDir(origin))
                .directory(dir).quiet(true).build().call();
        config.setRepositoryDir(repository, dir);
    }

    @Test
    public void testResetEmptyIndex() throws Exception {
        String rev = currentRevision();
        assertThat(filesInTheIndex(), is(emptyCollectionOf(GitStatus.class)));
        createBuilder().build().call();
        assertThat(currentRevision(), equalTo(rev));
        assertThat(filesInTheIndex(), is(emptyCollectionOf(GitStatus.class)));
    }

    @Test
    public void testResetModifiedIndex() throws Exception {
        String rev = currentRevision();
        List<String> files = addTwoFiles(repository);
        assertIndexContains(new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(0)),
                new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));

        createBuilder().build().call();
        assertThat(currentRevision(), equalTo(rev));
        assertIndexContains(new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(0)),
                new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));
    }

    @Test
    public void testResetHardToCommit() throws Exception {
        String rev = "0a943a29376f2336b78312d99e65da17048951db";
        List<String> files = addTwoFiles(repository);
        assertIndexContains(new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(0)),
                new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));

        createBuilder().hard().commit(rev).build().call();
        assertThat(currentRevision(), equalTo(rev));
        assertIndexContains(new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));
    }

    @Test
    public void testResetToCommit() throws Exception {
        testResetMixed(createBuilder());
    }

    @Test
    public void testResetMixedToCommit() throws Exception {
        testResetMixed(createBuilder().mixed());
    }

    private void testResetMixed(GitResetBuilder builder) throws IOException {
        // reset index and working directory to a known commit
        createBuilder().hard().commit("0a943a29376f2336b78312d99e65da17048951db").build().call();
        List<String> files = addTwoFiles(repository);

        // check reset mixed 0a943a2 -> e0a3f6d
        String rev = "e0a3f6d8c804c375f49d7609328111e8dc75a4bc";
        builder.commit(rev).build().call();
        assertThat(currentRevision(), equalTo(rev));
        assertIndexContains(new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, "binary/D.zip"),
                new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));
    }

    @Test
    public void testResetSoft() throws Exception {
        // reset index and working directory to a known commit
        createBuilder().hard().commit("0a943a29376f2336b78312d99e65da17048951db").build().call();
        List<String> files = addTwoFiles(repository);

        // check reset soft 0a943a2 -> e0a3f6d
        String rev = "e0a3f6d8c804c375f49d7609328111e8dc75a4bc";
        createBuilder().soft().commit(rev).build().call();
        assertThat(currentRevision(), equalTo(rev));
        assertIndexContains(new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, "binary/D.zip"),
                new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(0)),
                new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));
    }

    @Test
    public void testResetQuietly() throws Exception {
        createBuilder().hard().commit("0a943a29376f2336b78312d99e65da17048951db").build().call();

        String rev = "9c05f43f859375e392d90d23a13717c16d0fdcda";
        StringHandler outputHandler = new StringHandler();
        createBuilder().commit(rev).build(outputHandler).call();
        assertThat(outputHandler.getOutput(), not(isEmptyString()));

        createBuilder().hard().commit("0a943a29376f2336b78312d99e65da17048951db").build().call();
        outputHandler = new StringHandler();
        createBuilder().commit(rev).quiet(true).build(outputHandler).call();
        assertThat(outputHandler.getOutput(), isEmptyString());
    }

    @Test
    public void testResetFileInIndex() throws Exception {
        List<String> files = addTwoFiles(repository);

        createBuilder().path(files.get(0)).build().call();
        assertIndexContains(new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(0)),
                new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));
    }

    @Test
    public void testResetFileNotInIndex() throws Exception {
        List<String> files = addTwoFiles(repository);

        createBuilder().path(files.get(1)).build().call();
        assertIndexContains(new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(0)),
                new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));
    }

    @Test
    public void testResetFiles() throws Exception {
        List<String> files = addTwoFiles(repository);

        createBuilder().paths(files).build().call();
        assertIndexContains(new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(0)),
                new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));
    }

    @Test
    public void testResetFiles2() throws Exception {
        List<String> files = addTwoFiles(repository);

        createBuilder().paths(files.get(0), files.get(1)).build().call();
        assertIndexContains(new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(0)),
                new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));
    }

    private String currentRevision() {
        return builderFactory.builder(repository).command("rev-list").argument("-1").argument("HEAD").build(new StringHandler()).call();
    }

    private List<GitStatus> filesInTheIndex() {
        CollectingStatusCallback callback = new CollectingStatusCallback();
        StatusOutputHandler handler = new StatusOutputHandler(new PageRequestImpl(0, 100), callback);
        builderFactory.builder(repository).status().porcelain(true).untracked("all").build(handler).call();
        return callback.getStatuses();
    }

    private void assertIndexContains(GitStatus... statuses) {
        assertThat(filesInTheIndex(), hasItems(statuses));
    }

    private List<String> addTwoFiles(Repository repository) throws IOException {
        File dir = config.getRepositoryDir(repository);
        File file1 = File.createTempFile("test", ".txt", dir);
        File file2 = File.createTempFile("test", ".txt", dir);
        Files.write("content-file1", file1, Charsets.US_ASCII);
        Files.write("content-file2", file2, Charsets.US_ASCII);
        file1.deleteOnExit();
        file2.deleteOnExit();
        builderFactory.builder(repository).add().path(file1.getName()).build().call();
        return Arrays.asList(file1.getName(), file2.getName());
    }

    private GitResetBuilder createBuilder() {
        return builderFactory.builder(repository).reset();
    }
}
