package com.atlassian.bitbucket.mesh.git.remote;

import com.atlassian.bitbucket.mesh.AbstractWiredTest;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderFactory;
import com.atlassian.bitbucket.mesh.git.builder.remote.GitRemote;
import com.atlassian.bitbucket.mesh.git.exception.CommandFailedException;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.junit.TemporaryDirectory;
import com.atlassian.bitbucket.mesh.process.handler.StringStdioHandler;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.test.annotation.DirtiesRepo;
import com.atlassian.bitbucket.mesh.test.annotation.Repo;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.nio.file.Path;

public class RemoteCommandTest extends AbstractWiredTest {

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

    @Autowired
    private GitProcessBuilderFactory builderFactory;
    @Repo
    private Repository repository;
    @Repo
    private Repository remoteRepository;

    @Test
    @DirtiesRepo("repository")
    public void testAddAndRemove() {
        String remoteName = "test-remote";
        Path remoteDir = remoteRepository.getPath();
        createBuilder().add(remoteName, remoteDir).build().run();

        assertEquals(remoteDir.toAbsolutePath().toString(), getConfig("remote." + remoteName + ".url"));
        assertNotNull(getConfig("remote." + remoteName + ".fetch"));
        assertNull(getConfig("remote." + remoteName + ".pushurl"));

        createBuilder().rm(remoteName).build().run();
        assertNull(getConfig("remote." + remoteName + ".url"));
        assertNull(getConfig("remote." + remoteName + ".fetch"));
        assertNull(getConfig("remote." + remoteName + ".pushurl"));
    }

    @Test
    @DirtiesRepo("repository") // tests the remote().add(String, String) variant
    public void testAddAndRemove2() {
        String remoteName = "test-remote";
        String remoteDir = remoteRepository.getPath().toAbsolutePath().toString();
        createBuilder().add(remoteName, remoteDir).build().run();

        assertEquals(remoteDir, getConfig("remote." + remoteName + ".url"));
        assertNotNull(getConfig("remote." + remoteName + ".fetch"));
        assertNull(getConfig("remote." + remoteName + ".pushurl"));

        createBuilder().rm(remoteName).build().run();
        assertNull(getConfig("remote." + remoteName + ".url"));
        assertNull(getConfig("remote." + remoteName + ".fetch"));
        assertNull(getConfig("remote." + remoteName + ".pushurl"));
    }

    @Test
    @DirtiesRepo("repository")
    public void testAddAndSetUrl() {
        String remoteName = "test-remote";
        createBuilder().add(remoteName, remoteRepository.getPath()).build().run();
        String remoteFetchConfig = getConfig("remote." + remoteName + ".fetch");
        assertNotNull(remoteFetchConfig);
        assertNull(getConfig("remote." + remoteName + ".pushurl"));

        Path newRemoteDir = tempDir.getRoot();
        createBuilder().setUrl(remoteName, newRemoteDir).build().run();

        assertEquals(newRemoteDir.toString(), getConfig("remote." + remoteName + ".url"));
        assertEquals(remoteFetchConfig, getConfig("remote." + remoteName + ".fetch"));
        assertNull(getConfig("remote." + remoteName + ".pushurl"));
    }

    @Test
    @DirtiesRepo("repository") // tests the remote().setUrl(String, String) variant
    public void testAddAndSetUrl2() {
        String remoteName = "test-remote";
        createBuilder().add(remoteName, remoteRepository.getPath()).build().run();
        String remoteFetchConfig = getConfig("remote." + remoteName + ".fetch");
        assertNotNull(remoteFetchConfig);
        assertNull(getConfig("remote." + remoteName + ".pushurl"));

        String newRemoteDir = tempDir.getRoot().toString();
        createBuilder().setUrl(remoteName, newRemoteDir).build().run();

        assertEquals(newRemoteDir, getConfig("remote." + remoteName + ".url"));
        assertEquals(remoteFetchConfig, getConfig("remote." + remoteName + ".fetch"));
        assertNull(getConfig("remote." + remoteName + ".pushurl"));
    }

    @Test
    @DirtiesRepo("repository")
    public void testAddAndSetPushUrl() {
        String remoteName = "test-remote";
        Path remoteDir = remoteRepository.getPath();
        createBuilder().add(remoteName, remoteDir).build().run();
        String remoteFetchConfig = getConfig("remote." + remoteName + ".fetch");
        assertNotNull(remoteFetchConfig);
        assertNull(getConfig("remote." + remoteName + ".pushurl"));

        String newPushRemoteDir = tempDir.getRoot().toString();
        createBuilder().setUrl(remoteName, newPushRemoteDir).push(true).build().run();

        assertEquals(remoteDir.toAbsolutePath().toString(), getConfig("remote." + remoteName + ".url"));
        assertEquals(remoteFetchConfig, getConfig("remote." + remoteName + ".fetch"));
        assertEquals(newPushRemoteDir, getConfig("remote." + remoteName + ".pushurl"));
    }

    @Test
    @DirtiesRepo("repository")
    public void testAddWithNonExistentDir() throws Exception {
        String remoteName = "test-remote";
        Path remoteDir = tempDir.getRoot();
        MoreFiles.deleteRecursively(remoteDir);

        createBuilder().add(remoteName, remoteDir).build().run();
        assertEquals(remoteDir.toString(), getConfig("remote." + remoteName + ".url"));
        assertNotNull(getConfig("remote." + remoteName + ".fetch"));
        assertNull(getConfig("remote." + remoteName + ".pushurl"));
    }

    @Test
    @DirtiesRepo("repository")
    public void testSetUrlToNonExistentDir() throws Exception {
        String remoteName = "test-remote";
        Path remoteDir = tempDir.newFolder();
        Path remoteDir2 = tempDir.newFolder();
        MoreFiles.deleteRecursively(remoteDir2);

        createBuilder().add(remoteName, remoteDir).build().run();
        createBuilder().setUrl(remoteName, remoteDir2).build().run();
        assertEquals(remoteDir2.toString(), getConfig("remote." + remoteName + ".url"));
        assertNotNull(getConfig("remote." + remoteName + ".fetch"));
        assertNull(getConfig("remote." + remoteName + ".pushurl"));
    }

    @Test
    @DirtiesRepo("repository")
    public void testSetPushUrlToNonExistentDir() throws Exception {
        String remoteName = "test-remote";
        Path remoteDir = tempDir.newFolder();
        Path remoteDir2 = tempDir.newFolder();
        MoreFiles.deleteRecursively(remoteDir2);

        createBuilder().add(remoteName, remoteDir).build().run();
        createBuilder().setUrl(remoteName, remoteDir2).push(true).build().run();
        assertEquals(remoteDir.toString(), getConfig("remote." + remoteName + ".url"));
        assertNotNull(getConfig("remote." + remoteName + ".fetch"));
        assertEquals(remoteDir2.toString(), getConfig("remote." + remoteName + ".pushurl"));
    }

    @Test(expected = CommandFailedException.class)
    @DirtiesRepo("repository")
    public void testAddTwice() throws Exception {
        String remoteName = "test-remote";
        Path remoteDir = tempDir.newFolder();
        Path remoteDir2 = tempDir.newFolder();
        try {
            createBuilder().add(remoteName, remoteDir).build().run();
        } catch (Exception e) {
            fail("Unexpected exception: " + e.getMessage());
        }
        createBuilder().add(remoteName, remoteDir2).build().run();
    }

    @Test(expected = CommandFailedException.class)
    public void testSetUrlOnNonExistentRemote() {
        createBuilder().setUrl("test-remote" + System.currentTimeMillis(), tempDir.getRoot()).build().run();
    }

    @Test(expected = CommandFailedException.class)
    public void testRemoveNonExistentRemote() {
        createBuilder().rm("test-remote" + System.currentTimeMillis()).build().run();
    }

    private String getConfig(String property) {
        try {
            return builderFactory.builder(repository).config().get(property).build(new StringStdioHandler()).run();
        } catch (CommandFailedException e) {
            return null;
        }
    }

    private GitRemote createBuilder() {
        return builderFactory.builder(repository).remote();
    }
}
