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

import com.atlassian.bitbucket.scm.git.command.merge.GitMergeException;
import com.atlassian.bitbucket.content.ContentTreeContext;
import com.atlassian.bitbucket.content.SimplePath;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.util.MoreCollectors;
import com.atlassian.stash.internal.scm.git.AbstractGitTest;
import com.atlassian.stash.internal.scm.git.command.merge.MergeOutputHandler;
import com.atlassian.stash.internal.scm.git.porcelain.GitPorcelain;
import com.atlassian.stash.internal.scm.git.porcelain.WorkTreeRequest;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.annotation.Repo;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.lsfiles.GitLsFilesBuilder;
import com.atlassian.bitbucket.scm.git.command.merge.conflict.GitMergeConflict;
import com.atlassian.bitbucket.scm.git.command.merge.GitMergeFastForward;
import com.atlassian.bitbucket.scm.git.command.merge.conflict.SubmoduleGitMergeConflict;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.File;
import java.util.List;
import java.util.stream.Collectors;

import static com.atlassian.bitbucket.util.MoreCollectors.toImmutableList;

public class LsFilesCommandTest extends AbstractGitTest {

    private static final List<StagedFile> REPOSITORY_FILES = ImmutableList.of(
            new StagedFile("350c8183602cdc40b962fba9a716e0ed583e28b2", new SimplePath(".gitmodules"), 0),
            new StagedFile("35fa3631f284086ff66dcd7cba96edbc4eaadab6", new SimplePath("Weekly_9843.png"), 0),
            new StagedFile("05a5ef901acf2e5d2d22e7251e3618f54c372843", new SimplePath("submodule"), 0));

    @Mock
    private ContentTreeContext context;

    @Repo("classpath:git/merge.zip")
    private Repository repository;

    @Autowired
    private GitCommandBuilderFactory builderFactory;
    @Autowired
    private I18nService i18nService;
    @Autowired
    private GitPorcelain porcelain;
    private File workTree;

    @Before
    public void setup() {
        workTree = porcelain.workTree(
                new WorkTreeRequest.Builder(repository, "df2fc897281251b855c7a8ab663ff7c45689afbb")
                        .branch("branch_that_has_submodule_at_different_commit_trgt")
                        .build());
    }

    @Test
    public void testLsFiles() throws Exception {
        List<String> paths = createBuilder().build(new SimpleLsFilesOutputHandler()).call();

        assertNotNull(paths);
        assertEquals(REPOSITORY_FILES.size(), paths.size());
        for (StagedFile repositoryFile : REPOSITORY_FILES) {
            assertTrue(paths.contains(repositoryFile.getPath().toString()));
        }
    }

    @Test
     public void testLsFilesStaged() throws Exception {
        List<StagedFile> actualStagedFiles = createBuilder().stage(true).build(new StagedLsFilesOutputHandler()).call();

        assertNotNull(actualStagedFiles);
        assertEquals(REPOSITORY_FILES.size(), actualStagedFiles.size());
        assertTrue(actualStagedFiles.containsAll(REPOSITORY_FILES));
    }

    @Test
    public void testLsFilesStagedSinglePath() throws Exception {
        StagedFile stagedFile = REPOSITORY_FILES.get(0);

        List<StagedFile> actualStagedFiles = createBuilder().stage(true).path(stagedFile.getPath().toString())
                .build(new StagedLsFilesOutputHandler()).call();

        assertNotNull(actualStagedFiles);
        assertEquals(1, actualStagedFiles.size());
        assertTrue(actualStagedFiles.contains(stagedFile));
    }

    @Test
    public void testLsFilesStagedMultiplePaths() throws Exception {
        List<StagedFile> stagedFiles = REPOSITORY_FILES.subList(0, 2);

        List<StagedFile> actualStagedFiles = createBuilder().stage(true)
                .paths(stagedFiles.stream()
                        .map(StagedFile::getPath)
                        .map(Object::toString)
                        .collect(toImmutableList()))
                .build(new StagedLsFilesOutputHandler()).call();

        assertNotNull(actualStagedFiles);
        assertEquals(stagedFiles.size(), actualStagedFiles.size());
        assertTrue(actualStagedFiles.containsAll(stagedFiles));
    }

    @Test
    public void testLsFilesStagedWithConflicts() throws Exception {
        SubmoduleGitMergeConflict conflict = mergeConflictingBranches();
        assertEquals("submodule", conflict.getPath());

        List<StagedFile> actualStagedFiles = createBuilder().stage(true).path("submodule")
                .build(new StagedLsFilesOutputHandler()).call();

        assertNotNull(actualStagedFiles);
        assertEquals(3, actualStagedFiles.size());
        assertTrue(actualStagedFiles.contains(new StagedFile("10bb6413ee6b40206fd37e5ad76a3dde5f71d631",
                new SimplePath("submodule"), 1)));
        assertTrue(actualStagedFiles.contains(new StagedFile("05a5ef901acf2e5d2d22e7251e3618f54c372843",
                new SimplePath("submodule"), 2)));
        assertTrue(actualStagedFiles.contains(new StagedFile("628fee769309695340092be53697eb91428e392a",
                new SimplePath("submodule"), 3)));
    }

    private GitLsFilesBuilder createBuilder() {
        return builderFactory.builder(repository).lsFiles().workingDirectory(workTree);
    }

    private SubmoduleGitMergeConflict mergeConflictingBranches() {
        try {
            builderFactory.builder().merge()
                    .normal()
                    .author("Git LsFiles Command Test", "git@lsfiles.test")
                    .commit("ee85213ec42077581cc408fee21cf2dc2278858b")
                    .ff(GitMergeFastForward.OFF)
                    .log(true)
                    .message("branch_that_has_submodule_at_different_commit_src merge")
                    .workingDirectory(workTree)
                    .build(new MergeOutputHandler(i18nService))
                    .call();
            throw new RuntimeException("Merging branch_that_has_submodule_at_different_commit_src into " +
                    "branch_that_has_submodule_at_different_commit_trgt should fail with a conflict in the submodule");
        } catch (GitMergeException e) {
            List<GitMergeConflict> conflicts = e.getConflicts();
            if (conflicts.size() == 1 && conflicts.get(0) instanceof SubmoduleGitMergeConflict) {
                return (SubmoduleGitMergeConflict) conflicts.get(0);
            }

            throw e;
        }
    }

}
