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

import com.atlassian.bitbucket.mesh.io.EncodingUtils;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.layout.StorageLayout;
import com.google.common.io.CharStreams;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.Silent.class)
@SuppressWarnings("ConstantConditions")
public class DefaultTranscoderTest {

    //When a GB18030-encoded file is read as UTF-8 these characters are all garbled
    public static final String ACCENTS = "àáâãäąćçčèéêëęēġğíîïiǰłńñòóôõöṙśšşùúûüźżžßÀÁÂÃÄĄĆÇÈÉÊËĘĞÍÎÏİŁŃÑÒÓÔÕÖŚŞÙÚÛÜŹŻ";

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

    @Mock
    private StorageLayout storageLayout;
    @InjectMocks
    private DefaultTranscoder transcoder;

    @Before
    public void setup() {
        when(storageLayout.getTempDir()).thenReturn(tempDir.getRoot().toPath());
    }

    @Test(expected = IllegalArgumentException.class)
    public void testTranscodeThrowsForBlankPath() throws IOException {
        transcoder.transcode("\n\t    ", mock(OutputStream.class));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testTranscodeThrowsForEmptyPath() throws IOException {
        transcoder.transcode("", mock(OutputStream.class));
    }

    @Test(expected = NullPointerException.class)
    public void testTranscodeThrowsForNullOutputStream() throws IOException {
        transcoder.transcode(tempDir.getRoot().toPath().toAbsolutePath() + "test.txt", null); //Passes a "valid" path
    }

    @Test(expected = IllegalArgumentException.class)
    public void testTranscodeThrowsForNullPath() throws IOException {
        transcoder.transcode(null, mock(OutputStream.class));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testTranscodeThrowsForPathsOutsideTmp() throws IOException {
        transcoder.transcode(new File(".").getAbsolutePath(), mock(OutputStream.class));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testTranscodeThrowsForPathsThatNavigateOutsideTmp() throws IOException {
        transcoder.transcode(MoreFiles.resolve(tempDir.getRoot().toPath(), "..", "directoryOutsideRoot").toString(),
                mock(OutputStream.class));
    }

    @Test
    public void testTranscodeWithBinary() throws IOException {
        Path binary = writeResource(getClass().getSimpleName() + ".class");

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        transcoder.transcode(binary.toString(), outputStream);

        assertTrue(outputStream.toString("UTF-8").startsWith(DefaultTranscoder.TRANSCODED_BINARY));
    }

    @Test
    public void testTranscode() throws Exception {
        Path gb18030 = writeResource("gb18030.txt");
        try (InputStream stream = new BufferedInputStream(Files.newInputStream(gb18030), 32768)) {
            //Verify that the source file is GB18030 encoded so we know that transcoding actually updated it
            assertEquals("GB18030", EncodingUtils.detectEncoding(stream, 32768));
            //Verify that the test string is "garbled" when reading the GB18030 file as UTF-8. This matches
            //what git diff and friends would do with the original text
            assertFalse(CharStreams.toString(new InputStreamReader(stream, StandardCharsets.UTF_8)).contains(ACCENTS));
        }

        //Transcode the file from GB18030 to UTF-8
        Path transcoded = tempDir.getRoot().toPath().resolve("transcoded.txt");
        try (OutputStream stream = Files.newOutputStream(transcoded)) {
            transcoder.transcode(gb18030.toString(), stream);
        }

        try (InputStream stream = new BufferedInputStream(Files.newInputStream(transcoded), 32768)) {
            //Verify that the transcoded output is now UTF-8
            assertEquals("UTF-8", EncodingUtils.detectEncoding(stream, 32768));
            //Verify that the transcoded output is, at least to some extent, sane
            assertTrue(CharStreams.toString(new InputStreamReader(stream, StandardCharsets.UTF_8)).contains(ACCENTS));
        }
    }

    private Path writeResource(String name) throws IOException {
        Path file = tempDir.newFile(name).toPath();
        try (InputStream inputStream = DefaultTranscoderTest.class.getResourceAsStream(name)) {
            Files.copy(inputStream, file, StandardCopyOption.REPLACE_EXISTING);
        }

        return file;
    }
}