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

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.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.status.GitStatusBuilder;
import com.atlassian.bitbucket.user.ApplicationUser;
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.List;

import static com.atlassian.stash.internal.scm.git.GitFactoriesTestHelper.StringHandler;
import static org.hamcrest.Matchers.hasItems;
import static org.mockito.Mockito.when;

public class StatusCommandTest extends AbstractGitTest {

    @Autowired
    private GitCommandBuilderFactory builderFactory;

    @Mock
    private ApplicationUser user;
    @Rule
    public TemporaryFolder tempFolder = new TemporaryFolder();

    @Before
    public void setUpUser() throws Exception {
        when(user.getDisplayName()).thenReturn("Git Commit Test");
        when(user.getEmailAddress()).thenReturn("git@commit.test");
    }

    @Test
    public void testUntracked() throws Exception {
        File dir = createRepository(tempFolder.getRoot());
        String file = tempFolder.newFile().getName();

        List<GitStatus> statuses = call(createBuilder(dir).porcelain(true));
        assertUntracked(statuses, file);

        statuses = call(createBuilder(dir).porcelain(true).untracked("all"));
        assertUntracked(statuses, file);

        statuses = call(createBuilder(dir).porcelain(true).untracked("no"));
        assertEmptyPage(statuses);

        statuses = call(createBuilder(dir).path(file).porcelain(true));
        assertUntracked(statuses, file);

        statuses = call(createBuilder(dir).path(file).porcelain(true).untracked("all"));
        assertUntracked(statuses, file);

        statuses = call(createBuilder(dir).path(file).porcelain(true).untracked("no"));
        assertEmptyPage(statuses);
    }

    private void assertUntracked(List<GitStatus> actual, String file) {
        assertStatus(actual, new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, file));
    }

    @Test
    public void testAdded() throws Exception {
        File dir = createRepository(tempFolder.getRoot());
        String file = tempFolder.newFile().getName();

        // add one file
        builderFactory.builder().workingDirectory(dir).add().path(file).build().call();

        List<GitStatus> page = call(createBuilder(dir).porcelain(true));
        assertAdded(page, file);

        page = call(createBuilder(dir).porcelain(true).untracked("all"));
        assertAdded(page, file);

        page = call(createBuilder(dir).porcelain(true).untracked("no"));
        assertAdded(page, file);

        page = call(createBuilder(dir).path(file).porcelain(true));
        assertAdded(page, file);

        page = call(createBuilder(dir).path(file).porcelain(true).untracked("all"));
        assertAdded(page, file);

        page = call(createBuilder(dir).path(file).porcelain(true).untracked("no"));
        assertAdded(page, file);
    }

    private void assertAdded(List<GitStatus> actual, String file) {
        assertStatus(actual, new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, file));
    }

    @Test
    public void testModified() throws Exception {
        File dir = createRepository(tempFolder.getRoot());
        String file = tempFolder.newFile().getName();

        // modify one file
        builderFactory.builder().workingDirectory(dir).add().paths(file).build().call();
        builderFactory.builder().workingDirectory(dir).commit().author(user).message("abc").build().call();
        Files.write("abc", new File(dir, file), Charsets.US_ASCII);

        List<GitStatus> statuses = call(createBuilder(dir).porcelain(true));
        assertModified(statuses, file);

        statuses = call(createBuilder(dir).porcelain(true).untracked("all"));
        assertModified(statuses, file);

        statuses = call(createBuilder(dir).porcelain(true).untracked("no"));
        assertModified(statuses, file);

        statuses = call(createBuilder(dir).path(file).porcelain(true));
        assertModified(statuses, file);

        statuses = call(createBuilder(dir).path(file).porcelain(true).untracked("all"));
        assertModified(statuses, file);

        statuses = call(createBuilder(dir).path(file).porcelain(true).untracked("no"));
        assertModified(statuses, file);
    }

    private void assertModified(List<GitStatus> actual, String file) {
        assertStatus(actual, new DefaultGitStatus(GitFileStatus.UNMODIFIED, GitFileStatus.MODIFIED, file));
    }

    @Test
    public void testRemovedInIndex() throws Exception {
        File dir = createRepository(tempFolder.getRoot());
        String file = tempFolder.newFile().getName();

        // remove one file
        builderFactory.builder().workingDirectory(dir).add().paths(file).build().call();
        builderFactory.builder().workingDirectory(dir).commit().author(user).message("abc").build().call();
        builderFactory.builder().workingDirectory(dir).rm().path(file).build().call(); // removed in the index

        List<GitStatus> statuses = call(createBuilder(dir).porcelain(true));
        assertRemovedFromIndex(statuses, file);

        statuses = call(createBuilder(dir).porcelain(true).untracked("all"));
        assertRemovedFromIndex(statuses, file);

        statuses = call(createBuilder(dir).porcelain(true).untracked("no"));
        assertRemovedFromIndex(statuses, file);

        statuses = call(createBuilder(dir).paths(file).porcelain(true));
        assertRemovedFromIndex(statuses, file);

        statuses = call(createBuilder(dir).paths(file).porcelain(true).untracked("all"));
        assertRemovedFromIndex(statuses, file);

        statuses = call(createBuilder(dir).paths(file).porcelain(true).untracked("no"));
        assertRemovedFromIndex(statuses, file);
    }

    private void assertRemovedFromIndex(List<GitStatus> actual, String file) {
        assertStatus(actual, new DefaultGitStatus(GitFileStatus.DELETED, GitFileStatus.UNMODIFIED, file));
    }

    @Test
    public void testRemovedInWorkingDir() throws Exception {
        File dir = createRepository(tempFolder.getRoot());
        String file = tempFolder.newFile().getName();

        // remove one file
        builderFactory.builder().workingDirectory(dir).add().paths(file).build().call();
        builderFactory.builder().workingDirectory(dir).commit().author(user).message("abc").build().call();
        assertTrue(new File(dir, file).delete());

        List<GitStatus> statuses = call(createBuilder(dir).porcelain(true));
        assertRemovedFromWorkingDir(statuses, file);

        statuses = call(createBuilder(dir).porcelain(true).untracked("all"));
        assertRemovedFromWorkingDir(statuses, file);

        statuses = call(createBuilder(dir).porcelain(true).untracked("no"));
        assertRemovedFromWorkingDir(statuses, file);

        statuses = call(createBuilder(dir).paths(file).porcelain(true));
        assertRemovedFromWorkingDir(statuses, file);

        statuses = call(createBuilder(dir).paths(file).porcelain(true).untracked("all"));
        assertRemovedFromWorkingDir(statuses, file);

        statuses = call(createBuilder(dir).paths(file).porcelain(true).untracked("no"));
        assertRemovedFromWorkingDir(statuses, file);
    }

    private void assertRemovedFromWorkingDir(List<GitStatus> statuses, String file) {
        assertStatus(statuses, new DefaultGitStatus(GitFileStatus.UNMODIFIED, GitFileStatus.DELETED, file));
    }

    @Test
    public void testMoved() throws Exception {
        File dir = createRepository(tempFolder.getRoot());
        String file = tempFolder.newFile().getName();
        String newname = "newname.test";

        // move one file
        builderFactory.builder().workingDirectory(dir).add().paths(file).build().call();
        builderFactory.builder().workingDirectory(dir).commit().author(user).message("abc").build().call();
        builderFactory.builder().workingDirectory(dir).command("mv").argument(file).argument(newname).build(new StringHandler()).call();

        List<GitStatus> statuses = call(createBuilder(dir).porcelain(true));
        assertMoved(file, newname, statuses);

        statuses = call(createBuilder(dir).porcelain(true));
        assertMoved(file, newname, statuses);

        statuses = call(createBuilder(dir).porcelain(true).untracked("all"));
        assertMoved(file, newname, statuses);

        statuses = call(createBuilder(dir).porcelain(true).untracked("no"));
        assertMoved(file, newname, statuses);

        statuses = call(createBuilder(dir).paths(file, newname).porcelain(true));
        assertMoved(file, newname, statuses);

        statuses = call(createBuilder(dir).paths(file, newname).porcelain(true).untracked("all"));
        assertMoved(file, newname, statuses);

        statuses = call(createBuilder(dir).paths(file, newname).porcelain(true).untracked("no"));
        assertMoved(file, newname, statuses);

        // status for each individual file
        statuses = call(createBuilder(dir).paths(file).porcelain(true));
        assertStatus(statuses, new DefaultGitStatus(GitFileStatus.DELETED, GitFileStatus.UNMODIFIED, file));
        statuses = call(createBuilder(dir).paths(newname).porcelain(true));
        assertStatus(statuses, new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, newname));
    }

    private void assertMoved(String originalName, String newName, List<GitStatus> statuses) {
        assertStatus(statuses, new DefaultGitStatus(GitFileStatus.RENAMED, GitFileStatus.UNMODIFIED, originalName, newName));
    }

    private File createRepository(File root) throws IOException {
        builderFactory.builder().init().directory(root.getAbsolutePath()).bare(false).build().call();
        return root;
    }

    private void assertEmptyPage(List<GitStatus> actual) {
        assertEquals(0, actual.size());
    }

    private void assertStatus(List<GitStatus> actual, GitStatus... expected) {
        assertEquals(expected.length, actual.size());
        assertThat(actual, hasItems(expected));
    }

    private List<GitStatus> call(GitStatusBuilder builder) {
        CollectingStatusCallback callback = new CollectingStatusCallback();
        StatusOutputHandler handler = new StatusOutputHandler(new PageRequestImpl(0, 100), callback);
        builder.build(handler).call();
        return callback.getStatuses();
    }

    private GitStatusBuilder createBuilder(File dir) {
        return builderFactory.builder().workingDirectory(dir).status();
    }

}
