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

import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.CommandFailedException;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.add.GitAddBuilder;
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.util.ConfigurableGitScmConfig;
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.ArrayList;
import java.util.List;

import static com.atlassian.stash.internal.scm.git.GitFactoriesTestHelper.*;
import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.hasItems;

public class AddCommandTest extends AbstractGitTest {

    @Rule
    public TemporaryFolder tempFolder = new TemporaryFolder();

    @Autowired
    private ConfigurableGitScmConfig config;
    private File directory;
    @Autowired
    private GitCommandBuilderFactory builderFactory;
    @Mock
    private Repository repository;

    @Before
    public void setUp() throws Exception {
        directory = createEmptyRepository(tempFolder.getRoot(), builderFactory);

        config.setRepositoryDir(repository, directory);
    }

    @Test
    public void testAddNewFile() throws Exception {
        List<String> files = createFiles(2);
        createBuilder().path(files.get(0)).build().call();

        assertStatus(files, new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(0)),
                            new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(1)));
    }

    @Test
    public void testAddNewFiles() throws Exception {
        List<String> files = createFiles(3);
        createBuilder().paths(files.subList(0, 2)).build().call();

        assertStatus(files, new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(0)),
                            new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(1)),
                            new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(2)));
    }

    @Test
    public void testAddNewFiles2() throws Exception {
        List<String> files = createFiles(3);
        createBuilder().paths(files.get(0), files.get(1)).build().call();

        assertStatus(files, new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(0)),
                            new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(1)),
                            new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(2)));
    }

    @Test
    public void testAddAll() throws Exception {
        List<String> files = createFiles(2);
        createBuilder().all(true).build().call();

        assertStatus(files, new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(0)),
                            new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(1)));
    }

    @Test(expected = CommandFailedException.class)
    public void testAddNonExistentFile() throws Exception {
        List<String> files = createFiles(1);
        assertTrue(new File(directory, files.get(0)).delete());
        createBuilder().path(files.get(0)).build().call();
    }

    @Test
    public void testAddFilesAlreadyInIndex() throws Exception {
        List<String> files = createFiles(2);
        createBuilder().path(files.get(1)).build().call();

        assertStatus(files, new DefaultGitStatus(GitFileStatus.UNTRACKED, GitFileStatus.UNTRACKED, files.get(0)),
                            new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(1)));

        // add the remaining file to the index
        createBuilder().all(true).build().call();
        assertStatus(files, new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(0)),
                            new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(1)));

        // update a file
        Files.write("updated content", new File(directory, files.get(0)), Charsets.UTF_8);
        assertStatus(files, new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.MODIFIED, files.get(0)),
                            new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(1)));

        // add the file again to update the index
        createBuilder().path(files.get(0)).build().call();

        assertStatus(files, new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(0)),
                            new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, files.get(1)));
    }

    @Test
    public void testAddFileWithCJKName() throws Exception {
        File file = createTempFile("測試-add", directory, "content");
        createBuilder().path(file.getName()).build().call();

        assertStatus(file.getName(), new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED,
                file.getName().replace("測試", "\"\\346\\270\\254\\350\\251\\246").concat("\"")));
    }

    @Test
    public void testForceAddIgnoredFile() throws Exception {
        Files.write("*.txt", new File(directory, ".gitignore"), Charsets.UTF_8);
        Files.write("Hello, World!", new File(directory, "hello.txt"), Charsets.UTF_8);

        GitAddBuilder builder = createBuilder().path("hello.txt");
        try {
            builder.build().call();

            fail("Adding an ignored file should fail without --force");
        } catch (CommandFailedException e) {
            assertStatus("hello.txt"); //The file should not show up at all, in git status

            builder.force(true).build().call(); //Use git add --force, and verify the file is added
            assertStatus("hello.txt", new DefaultGitStatus(GitFileStatus.ADDED, GitFileStatus.UNMODIFIED, "hello.txt"));
        }
    }

    private void assertStatus(String file, GitStatus... expected) {
        assertStatus(singletonList(file), expected);
    }

    private void assertStatus(List<String> files, GitStatus... expected) {
        List<GitStatus> actual = getStatus(directory, files, builderFactory);
        assertEquals(expected.length, actual.size());
        assertThat(actual, hasItems(expected));
    }

    private GitAddBuilder createBuilder() {
        return builderFactory.builder(repository).add();
    }

    private List<String> createFiles(int n) throws IOException {
        List<String> files = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            File tempFile = createTempFile("test-add", directory, "content");
            files.add(tempFile.getName());
        }
        return files;
    }

}
