package com.atlassian.bitbucket.mesh.git;

import com.atlassian.bitbucket.mesh.AbstractWiredTest;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderFactory;
import com.atlassian.bitbucket.mesh.git.exception.CommandFailedException;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.test.annotation.DirtiesRepo;
import com.atlassian.bitbucket.mesh.test.annotation.Repo;
import com.google.common.collect.ImmutableList;
import com.google.common.io.LineProcessor;
import org.apache.commons.lang3.StringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;

import static com.atlassian.bitbucket.mesh.git.GitConstants.*;
import static com.google.common.io.CharStreams.readLines;

public class DefaultGitRepositoryLayoutTest extends AbstractWiredTest {

    @Rule
    public final ExpectedException thrown = ExpectedException.none();

    @Autowired
    private GitProcessBuilderFactory builderFactory;
    @Repo
    private Repository repository;
    @Autowired
    private GitRepositoryLayout repositoryLayout;

    @DirtiesRepo
    @Test
    public void testEditConfig() throws Exception {
        Path repositoryDir = repository.getPath();
        Path lockFile = repositoryDir.resolve(PATH_CONFIG + ".lock");

        //Prepare the config file
        Files.write(repositoryDir.resolve("config"), ImmutableList.of(
                "[core]",
                "\trepositoryformatversion = 0",
                "\tfilemode = true",
                "\tbare = true",
                "\tignorecase = true",
                "\tpackedgitlimit = 256m",
                "\tpackedgitwindowsize = 256m",
                "[http]",
                "\treceivepack = true"), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);

        repositoryLayout.editConfig(repository, ((reader, writer) -> {
            assertNotNull(reader); //config exists, so the reader should be provided
            assertNotNull(writer); //The writer should always be provided
            assertTrue(Files.isRegularFile(lockFile)); //Verify lock is in place

            readLines(reader, new LineProcessor<Void>() {

                @Override
                public Void getResult() {
                    return null;
                }

                @Override
                public boolean processLine(@Nonnull String line) throws IOException {
                    //Drop the [http] section, mutate the packedgitwindowsize and copy over everything else
                    if (line.equals("[http]") || line.contains("receivepack")) {
                        return true;
                    }
                    if (line.contains("packedgitwindowsize")) {
                        line = "\tpackedgitwindowsize = 32m";
                    }
                    writer.write(line);
                    writer.write('\n');

                    return true;
                }
            });
        }));

        assertFalse(Files.exists(lockFile)); //Verify the lock was removed

        List<String> config = builderFactory.builder(repository).config()
                .list()
                .build()
                .run();
        assertNotNull(config);
        //Verify our packedgitwindowsize mutation
        assertTrue(config.contains(GitConstants.CONFIG_CORE_PACKED_GIT_WINDOW_SIZE + "=32m"));
        //Verify that the [http] section was dropped
        assertFalse(config.stream().anyMatch(entry -> StringUtils.startsWith(entry, "http.")));
    }

    @Test
    public void testEditConcurrently() throws Exception {
        thrown.expect(OverlappingFileLockException.class);

        //Create the lock on disk but don't open it or acquire any type of process lock, to
        //verify our locking code fails if another lock exists at all
        Path lockFile = repository.getPath().resolve(PATH_CONFIG + ".lock");
        Files.createFile(lockFile);

        try {
            repositoryLayout.editConfig(repository, (reader, writer) -> fail("Locking for edit should have failed"));
        } finally {
            Files.delete(lockFile);
        }
    }

    @Test
    public void testEditConfigLocking() throws Exception {
        thrown.expect(CommandFailedException.class);
        thrown.expectMessage("could not lock config file");

        repositoryLayout.editConfig(repository, (reader, writer) -> {
            //editConfig should use config.lock to prevent any other process from modifying
            //config at the same time, so this git config process should fail
            builderFactory.builder(repository).config()
                    .set(GitConstants.CONFIG_CORE_PACKED_GIT_WINDOW_SIZE, "32m")
                    .build()
                    .run();
        });
    }

    @Test
    public void testGetApplicationInfoDir() {
        Path expectedPath = repository.getPath().resolve(PATH_APPLICATION_INFO);

        assertEquals(expectedPath, repositoryLayout.getApplicationInfoDir(repository));
    }

    @Test
    public void testGetObjectsDir() {
        Path expectedPath = repository.getPath().resolve(PATH_OBJECTS);

        assertEquals(expectedPath, repositoryLayout.getObjectsDir(repository));
    }
}
