package com.atlassian.bitbucket.mesh.repository;

import com.atlassian.bitbucket.mesh.AbstractWiredExecutionContextTest;
import com.atlassian.bitbucket.mesh.git.GitAgent;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderFactory;
import com.atlassian.bitbucket.mesh.git.ref.Branch;
import com.atlassian.bitbucket.mesh.git.ref.RefUpdate;
import com.atlassian.bitbucket.mesh.git.worktree.CreateWorkTreeParameters;
import com.atlassian.bitbucket.mesh.git.worktree.InvalidWorkTreePathException;
import com.atlassian.bitbucket.mesh.git.worktree.PublishWorkTreeParameters;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.junit.TemporaryDirectory;
import com.atlassian.bitbucket.mesh.process.handler.LoggingStdioHandler;
import com.atlassian.bitbucket.mesh.process.handler.StringStdioHandler;
import com.atlassian.bitbucket.mesh.test.annotation.DirtiesRepo;
import com.atlassian.bitbucket.mesh.test.annotation.Repo;
import com.atlassian.bitbucket.mesh.util.Person;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Path;
import java.time.Duration;

import static java.nio.charset.StandardCharsets.UTF_16;
import static java.nio.charset.StandardCharsets.UTF_8;

public class DefaultWorkTreeManagerTest extends AbstractWiredExecutionContextTest {

    private static final String FILE_CONTENT = "example test content\n";
    private static final String FILE_SRC_PATH = "src/nested/file.txt";

    @Rule
    public final TemporaryDirectory tempDir = new TemporaryDirectory();

    @Resource
    private GitAgent agent;
    @Resource
    private GitProcessBuilderFactory builderFactory;
    @Repo
    private Repository repository;
    private WorkTree workTree;
    @Resource
    private WorkTreeManager workTreeManager;

    @Before
    public void setUp() throws Exception {
        CreateWorkTreeParameters parameters = new CreateWorkTreeParameters.Builder(repository, null,
                Duration.ofSeconds(60))
                .build();
        workTree = workTreeManager.create(parameters);
        Path file = workTree.getPath().resolve(FILE_SRC_PATH);
        MoreFiles.mkdir(file.getParent());
        MoreFiles.write(file, FILE_CONTENT, UTF_8);
    }

    @Test
    public void testClose() {
        workTreeManager.close(workTree);
        assertNull(workTreeManager.findById(workTree.getId()));
    }

    @Test
    public void testMkdir() {
        workTreeManager.mkdir(workTree, "relativePath");
        assertTrue(MoreFiles.dirExists(workTree.getPath().resolve("relativePath")));
    }

    @Test
    public void testMkdirExisting() {
        workTreeManager.mkdir(workTree, "src");
        assertTrue(MoreFiles.dirExists(workTree.getPath().resolve("src")));
    }

    @Test
    public void testMkdirExistingFile() {
        InvalidWorkTreePathException e = assertThrows(InvalidWorkTreePathException.class,
                () -> workTreeManager.mkdir(workTree, FILE_SRC_PATH));
        assertFalse("Leaking the work tree path",
                e.getMessage().contains((workTree).getPath().toString()));
        assertFalse("Leaking the work tree path",
                e.getPath().contains((workTree).getPath().toString()));
    }

    @DirtiesRepo
    @Test
    public void testPublish() {
        Branch oldHead = agent.resolveHead(repository);
        builderFactory.builder(workTree).add()
                .path(FILE_SRC_PATH)
                .build(new LoggingStdioHandler())
                .run();
        builderFactory.builder(workTree).commit()
                .author(new Person("New phone who dis?", "mheemskerk@atlassian.com"))
                .message("Added a file")
                .build(new StringStdioHandler())
                .run();
        PublishWorkTreeParameters parameters = new PublishWorkTreeParameters.Builder()
                .branch("master")
                .build();

        RefUpdate refUpdate = workTreeManager.publish(workTree, parameters);
        Branch newHead = agent.resolveHead(repository);

        assertEquals(refUpdate.getRefId(), "refs/heads/master");
        assertEquals(refUpdate.getOldHash(), oldHead.getLatestCommit());
        assertEquals(oldHead, newHead);
    }

    @DirtiesRepo
    @Test
    public void testUpdateRef() {
        builderFactory.builder(workTree).add()
                .path(FILE_SRC_PATH)
                .build(new LoggingStdioHandler())
                .run();
        builderFactory.builder(workTree).commit()
                .author(new Person("New phone who dis?", "mheemskerk@atlassian.com"))
                .message("Added a file")
                .build(new StringStdioHandler())
                .run();
        PublishWorkTreeParameters parameters = new PublishWorkTreeParameters.Builder()
                .branch("master")
                .build();
        RefUpdate refUpdate = workTreeManager.publish(workTree, parameters);

        workTreeManager.updateRef(workTree, parameters, refUpdate);

        Branch head = agent.resolveHead(repository);
        assertEquals(head.getLatestCommit(), refUpdate.getNewHash());
    }

    @Test
    public void testWriteToExistingFile() throws IOException {
        WritableByteChannel byteChannel = workTreeManager.writeFile(workTree, FILE_SRC_PATH);
        String fileContent = "new content\n";
        ByteBuffer buffer = ByteBuffer.allocate(fileContent.length() * 2);
        buffer.asCharBuffer().put(fileContent);
        byteChannel.write(buffer);
        byteChannel.close();
        assertEquals(fileContent,
                MoreFiles.toString(workTree.getPath().resolve(FILE_SRC_PATH), UTF_16));
    }
}