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

import com.atlassian.bitbucket.commit.SimpleMinimalCommit;
import com.atlassian.bitbucket.content.Blame;
import com.atlassian.bitbucket.content.FileContentCallback;
import com.atlassian.bitbucket.content.FileContext;
import com.atlassian.bitbucket.content.FileSummary;
import com.atlassian.bitbucket.scm.annotation.Repo;
import com.atlassian.stash.internal.scm.git.AbstractGitTest;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.BlameCommandParameters;
import com.atlassian.bitbucket.scm.Command;
import com.atlassian.bitbucket.scm.CommandResult;
import com.atlassian.bitbucket.scm.FileCommandParameters;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.GitCommandFactory;
import com.atlassian.bitbucket.scm.git.command.blame.GitBlame;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PageUtils;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.util.List;

import static org.mockito.Mockito.*;

public class BlameCommandTest extends AbstractGitTest {

    @Captor
    private ArgumentCaptor<List<Blame>> blameCaptor;
    @Autowired
    private GitCommandBuilderFactory builderFactory;
    @Autowired
    private GitCommandFactory commandFactory;
    @Repo
    private Repository repository;

    @Test
    public void testBlame() {
        Command<List<Blame>> command = commandFactory.blame(repository,
                new BlameCommandParameters.Builder()
                        .commitId("master")
                        .path("modification/mod_file.txt")
                        .build(),
                PageUtils.newRequest(0, 50));
        doTestIncrementalBlame(command);
    }

    @Test
    public void testFileWithBlame() throws Exception {
        FileContentCallback callback = mock(FileContentCallback.class);
        PageRequest pageRequest = PageUtils.newRequest(0, 50);

        Command<Void> command = commandFactory.file(repository,
                new FileCommandParameters.Builder()
                        .annotate(true)
                        .commitId("master")
                        .maxLineLength(15)
                        .path("modification/mod_file.txt")
                        .build(),
                callback,
                pageRequest);
        doTestPorcelainBlame(callback, command);
    }

    @Test
    public void testIncrementalBlame() {
        Command<List<Blame>> command = createBuilder()
                .incremental()
                .file("modification/mod_file.txt")
                .range(PageUtils.newRequest(0, 50))
                .rev("master")
                .build(new IncrementalBlameOutputHandler());
        doTestIncrementalBlame(command);
    }

    @Test
    public void testIncrementalBlameViaBuilder() {
        Command<List<Blame>> command = builderFactory.builder(repository)
                .argument("blame")
                .argument("-L")
                .argument("1,2") //Note: We won't get the automatic retry logic, so the page range must be exact!
                .argumentAfter("blame", "--incremental")
                .argument("--")
                .argumentBefore("--", "master")
                .argument("modification/mod_file.txt")
                .build(new IncrementalBlameOutputHandler());
        doTestIncrementalBlame(command);
    }

    @Test
    public void testIncrementalBlameWithBinaryFile() {
        Command<List<Blame>> command = createBuilder()
                .incremental()
                .file("binary/B.zip")
                .range(PageUtils.newRequest(0, 50))
                .rev("master")
                .build(new IncrementalBlameOutputHandler());
        List<Blame> blames = command.call();
        assertNotNull(blames);
        assertFalse(blames.isEmpty());
        assertEquals(1, blames.size());
        assertBlame("9c05f43f859375e392d90d23a13717c16d0fdcda", blames.get(0));
    }

    @Test
    public void testPorcelainBlame() throws Exception {
        FileContentCallback callback = mock(FileContentCallback.class);
        PageRequest pageRequest = PageUtils.newRequest(0, 50);

        Command<Void> command = createBuilder()
                .porcelain()
                .file("modification/mod_file.txt")
                .range(pageRequest)
                .rev("master")
                .build(new PorcelainBlameOutputHandler(callback, new FileContext.Builder("master").maxLineLength(15).build(), pageRequest));
        doTestPorcelainBlame(callback, command);
    }

    @Test
    public void testPorcelainBlameWithBinaryFile() throws Exception {
        FileContentCallback callback = mock(FileContentCallback.class);
        PageRequest pageRequest = PageUtils.newRequest(0, 50);

        Command<Void> command = createBuilder()
                .porcelain()
                .file("binary/B.zip")
                .range(pageRequest)
                .rev("master")
                .build(new PorcelainBlameOutputHandler(callback, new FileContext.Builder("master").maxLineLength(5000).build(), pageRequest));
        command.call();

        InOrder ordered = inOrder(callback);
        ordered.verify(callback).onBinary();
        ordered.verifyNoMoreInteractions();
    }

    private GitBlame createBuilder() {
        return builderFactory.builder(repository).blame();
    }

    private void doTestIncrementalBlame(Command<List<Blame>> command) {
        List<Blame> blames = command.call();
        assertNotNull(blames);
        assertFalse(blames.isEmpty());
        assertEquals(2, blames.size());
        assertBlame("e26cab95a50f6e99d2c46c498a23cee307cc8e27", blames.get(0));
        assertBlame("bf3346620ec9161977fc23dee1240a7991e64ca1", blames.get(1));
    }

    private void doTestPorcelainBlame(FileContentCallback callback, Command<Void> command)
            throws IOException {
        //Ensure the callback mock does not stop processing early
        doReturn(true).when(callback).onLine(anyInt(), anyString(), anyBoolean());

        command.call();

        InOrder ordered = inOrder(callback);
        ordered.verify(callback).onStart(isNotNull(FileContext.class));
        ordered.verify(callback).onLine(eq(1), eq("this is a test "), eq(true));
        ordered.verify(callback).onLine(eq(2), eq("another line"), eq(false));

        ArgumentCaptor<FileSummary> summaryCaptor = ArgumentCaptor.forClass(FileSummary.class);

        ordered.verify(callback).offerBlame(blameCaptor.capture());
        ordered.verify(callback).onEnd(summaryCaptor.capture());

        FileSummary summary = summaryCaptor.getValue();
        assertNotNull(summary);
        assertSame(CommandResult.SUCCEEDED, summary.getResult());
        ordered.verifyNoMoreInteractions();

        List<Blame> blames = blameCaptor.getValue();
        assertNotNull(blames);
        assertFalse(blames.isEmpty());
        assertEquals(2, blames.size());
        assertBlame("e26cab95a50f6e99d2c46c498a23cee307cc8e27", blames.get(0));
        assertBlame("bf3346620ec9161977fc23dee1240a7991e64ca1", blames.get(1));
    }
    
    private static void assertBlame(String commitId, Blame blame) {
        assertEquals(commitId, blame.getCommitId());
        assertEquals(commitId.substring(0, SimpleMinimalCommit.DEFAULT_DISPLAY_ID_LENGTH),
                blame.getCommitDisplayId());
    }
}
