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

import com.atlassian.bitbucket.mesh.git.submodule.SubmoduleConfig;
import com.atlassian.bitbucket.mesh.repository.Repository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

@RunWith(MockitoJUnitRunner.class)
public class SubmoduleCatFileStdioHandlerTest {

    @Mock(name = "repository")
    private Repository repository;

    @Test
    public void testParsing() throws Exception {
        try (InputStream inputStream = getClass().getResourceAsStream("gitmodules.txt")) {
            Map<String, SubmoduleConfig> modules = parseSubmodules(inputStream);
            assertEquals(2, modules.size());
            assertThat(modules, hasEntry(equalTo("rep_1"), allOf(
                    hasProperty("active", equalTo(false)),
                    hasProperty("branch", nullValue()),
                    hasProperty("path", equalTo("rep_1")),
                    hasProperty("url", equalTo("ssh://git@host:7999/PROJECT_1/rep_1.git")))));
            assertThat(modules, hasEntry(equalTo("service-api/src/rep_2"), allOf(
                    hasProperty("active", equalTo(true)),
                    hasProperty("branch", equalTo("main")),
                    hasProperty("path", equalTo("service-api/src/rep_2")),
                    hasProperty("url", equalTo("https://bitbucket.org/user/repo")))));
        }
    }

    @Test
    public void testParsingDefaultsEntriesWithNoEqualSignToTrue() throws Exception {
        Map<String, SubmoduleConfig> modules = parseSubmodules(
                "[submodule \"empty_branch\"]\n" +
                        "        active\n" + //The same as active=true
                        "        path=empty_branch\n" +
                        "        url=test");
        assertEquals(1, modules.size());
        assertThat(modules, hasEntry(equalTo("empty_branch"), allOf(
                hasProperty("active", equalTo(true)),
                hasProperty("path", equalTo("empty_branch")),
                hasProperty("url", equalTo("test")))));
    }

    @Test
    public void testParsingHandlesDuplicates() throws Exception {
        try (InputStream inputStream = getClass().getResourceAsStream("gitmodules-collision.txt")) {
            Map<String, SubmoduleConfig> modules = parseSubmodules(inputStream);
            assertEquals(2, modules.size());
            assertThat(modules, hasEntry(equalTo("dependencies/Carthage/Checkouts/IDZSwiftCommonCrypto"),
                    hasProperty("url", equalTo("https://github.com/iosdevzone/IDZSwiftCommonCrypto.git"))));
            assertThat(modules, hasEntry(equalTo("dependencies/Carthage/Checkouts/objective-c"),
                    hasProperty("url", equalTo("https://github.com/pubnub/objective-c.git"))));
        }
    }

    @Test
    public void testParsingHandlesMixedEolStyles() throws Exception {
        //Note that \r is not a line ending to Git, so that is not tested here
        Map<String, SubmoduleConfig> modules = parseSubmodules(
                "[submodule \"mixed_eols\"]\r\n" +
                        "        path=mixed_eols\n" +
                        "        url=test\r\n" +
                        "        branch=foo\n" +
                        "        active=false\r\n");
        assertEquals(1, modules.size());
        assertThat(modules, hasEntry(equalTo("mixed_eols"), allOf(
                hasProperty("active", equalTo(false)),
                hasProperty("branch", equalTo("foo")),
                hasProperty("path", equalTo("mixed_eols")),
                hasProperty("url", equalTo("test")))));
    }

    @Test
    public void testParsingIgnoresEntriesWithNoEqualSignAndTrailingComment() throws Exception {
        Map<String, SubmoduleConfig> modules = parseSubmodules(
                "[submodule \"empty_branch\"]\n" +
                        "        path=empty_branch\n" +
                        "        url=test\n" +
                        "        branch ; hahaha no value!"); //Trailing comments like this are invalid
        assertEquals(1, modules.size());
        assertThat(modules, hasEntry(equalTo("empty_branch"), allOf(
                hasProperty("active", equalTo(true)),
                hasProperty("branch", nullValue()), //The "branch" key should be ignored
                hasProperty("path", equalTo("empty_branch")),
                hasProperty("url", equalTo("test")))));
    }

    @Test
    public void testParsingIgnoresPathWithNoValue() throws Exception {
        Map<String, SubmoduleConfig> modules = parseSubmodules(
                "[submodule \"empty_path\"]\n" +
                        "        path=\n" +
                        "        url=test");
        assertTrue("No submodule should have been parsed", modules.isEmpty());
    }

    @Test
    public void testParsingIgnoresPathWithNoValueAndTrailingComment() throws Exception {
        Map<String, SubmoduleConfig> modules = parseSubmodules(
                "[submodule \"empty_path\"]\n" +
                        "        path= ; hahaha no value!\n" +
                        "        url=test");
        assertTrue("No submodule should have been parsed", modules.isEmpty());
    }

    @Test
    public void testParsingIgnoresUrlWithNoValue() throws Exception {
        Map<String, SubmoduleConfig> modules = parseSubmodules(
                "[submodule \"empty_url\"]\n" +
                        "        path=empty_url" +
                        "        url=");
        assertTrue("No submodule should have been parsed", modules.isEmpty());
    }

    @Test
    public void testParsingIgnoresUrlWithNoValueAndTrailingComment() throws Exception {
        Map<String, SubmoduleConfig> modules = parseSubmodules(
                "[submodule \"empty_url\"]\n" +
                        "        path=empty_url\n" +
                        "        url= ; hahaha no value!");
        assertTrue("No submodule should have been parsed", modules.isEmpty());
    }

    @Test
    public void testParsingIgnoresNonSubmodules() throws Exception {
        Map<String, SubmoduleConfig> modules = parseSubmodules(
                "[s-ub.sub-module \"not_important\"]\n" +
                        "        path=not_important ; hahaha not a submodule!" +
                        "        url=phony");
        assertTrue("No submodule should have been parsed", modules.isEmpty());
    }

    private Map<String, SubmoduleConfig> parseSubmodules(InputStream inputStream) throws IOException {
        SubmoduleCatFileStdioHandler handler = new SubmoduleCatFileStdioHandler(repository);

        ByteBuffer buffer = ByteBuffer.allocate(25);
        ReadableByteChannel channel = Channels.newChannel(inputStream);
        while (channel.read(buffer) > -1) {
            buffer.flip();

            handler.onStdout(buffer, buffer.remaining() < 25);

            buffer.compact();
        }

        Map<String, SubmoduleConfig> modules = handler.getResult();
        assertNotNull(modules);

        return modules;
    }

    private Map<String, SubmoduleConfig> parseSubmodules(String data) throws IOException {
        return parseSubmodules(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)));
    }
}