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

import com.atlassian.bitbucket.mesh.AbstractWiredTest;
import com.atlassian.bitbucket.mesh.config.ProcessConfig;
import com.atlassian.bitbucket.mesh.git.GitQuarantineRegistry;
import com.atlassian.bitbucket.mesh.git.GitRepositories;
import com.atlassian.bitbucket.mesh.git.binary.GitBinary;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.layout.StorageLayout;
import com.atlassian.bitbucket.mesh.process.ProcessCreator;
import com.atlassian.bitbucket.mesh.process.ProcessParameters;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.repository.RepositoryManager;
import com.atlassian.bitbucket.mesh.test.annotation.Repo;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;

import static com.atlassian.bitbucket.mesh.git.GitConstants.*;
import static org.hamcrest.CoreMatchers.*;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class DefaultGitProcessBuilderQuarantineTest extends AbstractWiredTest {

    @Resource
    private GitBinary binary;
    @Repo(GitRepositories.BINARY_FILES)
    private Repository binaryFiles;
    @Captor
    private ArgumentCaptor<ProcessParameters<?>> captor;
    @Mock
    private ProcessConfig processConfig;
    //Warning: This test relies on this being a mock, rather than a real ProcessCreator. Otherwise, since it
    //is building "git gc" commands, it would dirty the test repositories it uses
    @Mock
    private ProcessCreator processCreator;
    private GitQuarantineEnvironment quarantineEnvironment;
    private GitQuarantineRegistry quarantineRegistry;
    @Repo
    private Repository repository;
    @Resource
    private RepositoryManager repositoryManager;
    @Resource
    private StorageLayout storageLayout;
    @Repo(GitRepositories.SUBREPOSITORIES)
    private Repository submodules;

    @Before
    public void setup() throws IOException {
        when(processConfig.getEnvironmentVariableSize()).thenReturn(8000);

        Path incomingDir = MoreFiles.mkdir(MoreFiles.resolve(repository.getPath(), "objects", "incoming-abcd"));
        quarantineEnvironment = new GitQuarantineEnvironment(repository, incomingDir.toString(),
                repository.getPath().toString(), incomingDir.toString());
        quarantineRegistry = new GitQuarantineRegistry();
    }

    @Test
    public void testQuarantineAsTarget() {
        String quarantineId = quarantineRegistry.register(quarantineEnvironment);

        builder(repositoryManager.getById(quarantineId)).gc()
                .alternate(submodules)
                .build()
                .run();

        verify(processCreator).run(captor.capture());
        ProcessParameters<?> parameters = captor.getValue();
        Map<String, String> environment = parameters.getEnvironment();

        assertEquals(repository.getPath(), parameters.getWorkDir());
        String alternateString = environment.get(ENV_ALTERNATE_OBJECT_DIRS);
        assertThat(alternateString, allOf(
                containsString(submodules.getRelativePath(repository).toString()),
                containsString(quarantineEnvironment.getQuarantinePath())));
        assertEquals(quarantineEnvironment.getObjectDir(), environment.get(ENV_OBJECT_DIR));
        assertEquals(quarantineEnvironment.getQuarantinePath(), environment.get(ENV_QUARANTINE_PATH));
    }

    @Test
    public void testQuarantineAsTargetWhenEnvTooLong() throws IOException {
        String quarantineId = quarantineRegistry.register(quarantineEnvironment);

        //Set a really short maximum environment variable size to trigger the fallback behavior where a custom
        //object dir with an alternates file is created to work around the restriction
        when(processConfig.getEnvironmentVariableSize()).thenReturn(10);

        builder(repositoryManager.getById(quarantineId)).gc()
                .alternate(submodules)
                .build()
                .run();

        verify(processCreator).run(captor.capture());
        ProcessParameters<?> parameters = captor.getValue();
        Map<String, String> environment = parameters.getEnvironment();

        //The quarantine environment should still be set
        assertEquals(repository.getPath(), parameters.getWorkDir());
        assertEquals(quarantineEnvironment.getQuarantinePath(), environment.get(ENV_ALTERNATE_OBJECT_DIRS));
        assertEquals(quarantineEnvironment.getObjectDir(), environment.get(ENV_OBJECT_DIR));
        assertEquals(quarantineEnvironment.getQuarantinePath(), environment.get(ENV_QUARANTINE_PATH));

        //A temporary object dir should have been created with an alternates file that contains the absolute paths
        //to the object directories of the target repo (repository) and the alternates (alternate)
        String objectDir = environment.get(ENV_OBJECT_DIR);
        assertNotNull(objectDir);
        List<String> alternates = Files.readAllLines(Paths.get(objectDir).resolve("info").resolve("alternates"));
        assertThat(alternates, hasItems(
                repository.getPath().resolve("objects").toString(),
                submodules.getPath().resolve("objects").toString()));
    }

    @Test
    public void testQuarantineAsAlternate() {
        String quarantineId = quarantineRegistry.register(quarantineEnvironment);
        Repository quarantined = repositoryManager.getById(quarantineId);
        assertNotNull(quarantined);

        builder(binaryFiles)
                .alternate(quarantined)
                .alternate(submodules)
                .gc()
                .build()
                .run();

        verify(processCreator).run(captor.capture());
        ProcessParameters<?> parameters = captor.getValue();
        Map<String, String> environment = parameters.getEnvironment();

        assertEquals(binaryFiles.getPath(), parameters.getWorkDir());
        String alternateString = environment.get(ENV_ALTERNATE_OBJECT_DIRS);
        assertThat(alternateString, allOf(
                containsString(quarantineEnvironment.getQuarantinePath()),
                containsString(submodules.getRelativePath(binaryFiles).toString())));
        assertNull(environment.get(ENV_OBJECT_DIR));
        assertNull(environment.get(ENV_QUARANTINE_PATH));
    }

    @Test
    public void testQuarantineAsAlternateWhenEnvTooLong() throws IOException {
        String quarantineId = quarantineRegistry.register(quarantineEnvironment);
        Repository quarantined = repositoryManager.getById(quarantineId);
        assertNotNull(quarantined);

        //Set a really short maximum environment variable size to trigger the fallback behavior where a custom
        //object dir with an alternates file is created to work around the restriction
        when(processConfig.getEnvironmentVariableSize()).thenReturn(10);

        builder(binaryFiles)
                .alternate(quarantined)
                .alternate(submodules)
                .gc()
                .build()
                .run();

        verify(processCreator).run(captor.capture());
        ProcessParameters<?> parameters = captor.getValue();
        Map<String, String> environment = parameters.getEnvironment();

        assertEquals(binaryFiles.getPath(), parameters.getWorkDir());
        assertNull(environment.get(ENV_ALTERNATE_OBJECT_DIRS));
        assertNull(environment.get(ENV_QUARANTINE_PATH));

        //A temporary object dir should have been created with an alternates file that contains the absolute paths
        //to the object directories of the target repo (target) and the alternates (repository, otherAlternates)
        String objectDir = environment.get(ENV_OBJECT_DIR);
        assertNotNull(objectDir);
        List<String> alternates = Files.readAllLines(Paths.get(objectDir).resolve("info").resolve("alternates"));
        assertThat(alternates, hasItems(
                binaryFiles.getPath().resolve("objects").toString(),
                quarantined.getPath().resolve("objects").toString(),
                submodules.getPath().resolve("objects").toString()));

    }

    private DefaultGitProcessBuilder builder(Repository repository) {
        return new DefaultGitProcessBuilder(binary, processConfig, processCreator,
                quarantineRegistry, storageLayout, repository);
    }
}
