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

import com.atlassian.bitbucket.mesh.AbstractWiredTest;
import com.atlassian.bitbucket.mesh.event.EventPublisher;
import com.atlassian.bitbucket.mesh.git.GitAgent;
import com.atlassian.bitbucket.mesh.git.GitConstants;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilder;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderFactory;
import com.atlassian.bitbucket.mesh.layout.StorageLayout;
import com.atlassian.bitbucket.mesh.process.handler.LoggingStdioHandler;
import com.atlassian.bitbucket.mesh.process.handler.StringStdioHandler;
import com.atlassian.bitbucket.mesh.repository.DefaultPathRepository;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.transaction.TransactionManager;
import com.google.common.base.Charsets;
import com.google.common.io.ByteStreams;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.mockito.Mockito.*;

/**
 * An integration-style test that verifies all of the transcoding components when used in a real repository.
 * <p>
 * For simplicity, this test assembles the repository itself rather than using a zipped repository. The assumption
 * is that it's unlikely this testing will be extended. The downside is that this approach makes extending the test
 * difficult if that assumption is ever violated
 */
public class GitTranscodeTest extends AbstractWiredTest {

    public static final String TEST_PATH = "可以吃.txt";

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

    @Autowired
    private GitProcessBuilderFactory builderFactory;
    @Autowired
    private EventPublisher eventPublisher;
    private ExecutorService executorService;
    private Repository repository;
    private DefaultTranscodeServer server;
    private TranscodeService service;
    @Autowired
    private StorageLayout storageLayout;
    @Autowired
    private TransactionManager transactionManager;
    private Transcoder transcoder;

    @Before
    public void setup() throws IOException {
        repository = buildRepository(); //Done first to not do the other things in case this blows up

        executorService = Executors.newSingleThreadExecutor();
        transcoder = spy(new DefaultTranscoder(storageLayout));

        server = new DefaultTranscodeServer(executorService, transcoder, null, 0);
        server.start();

        service = new DefaultTranscodeService(builderFactory, eventPublisher, transactionManager);
    }

    @After
    public void tearDown() throws IOException {
        if (server != null) {
            server.stop();
        }
        if (executorService != null) {
            assertTrue(executorService.shutdownNow().isEmpty());
        }
    }

    @Test
    public void testTranscoding() throws Exception {
        //First, run the diff without transcoding and verify that it's junk
        assertFalse(service.isEnabled(repository));

        String diff = runDiff();
        assertFalse(diff.contains(DefaultTranscoderTest.ACCENTS));
        verifyNoInteractions(transcoder); //With transcoding disabled, the server should not be hit

        reset(transcoder); //Reset our spy

        //Next, enable transcoding and try the diff again. This time, the accents should be shown correctly
        service.setEnabled(repository, true);
        assertTrue(service.isEnabled(repository));

        diff = runDiff();
        assertTrue("Accents are missing from the transcoded diff output:\n" + diff,
                diff.contains(DefaultTranscoderTest.ACCENTS));
        verify(transcoder, times(2)).transcode(anyString(), any(OutputStream.class));

        reset(transcoder); //Reset our spy

        //And, lastly, disable transcoding and verify the diff is back to junk
        service.setEnabled(repository, false);
        assertFalse(service.isEnabled(repository));

        diff = runDiff();
        assertFalse("Accents are present in the diff output without transcoding:\n" + diff,
                diff.contains(DefaultTranscoderTest.ACCENTS));
        verifyNoInteractions(transcoder);
    }

    private String runDiff() {
        GitProcessBuilder builder = builderFactory.builder(repository);
        new TranscodeGitProcessBuilderConfigurer(server)
                .configure(builder);

        String diff = builder.diff()
                .ancestor(GitAgent.HEAD + "^")
                .rev(GitAgent.HEAD)
                .path(TEST_PATH)
                .build(new StringStdioHandler(StandardCharsets.UTF_8)) //Needs to use UTF-8 decoding explicitly
                .run();
        assertNotNull(diff);

        return diff;
    }

    private Repository buildRepository() throws IOException {
        Path workDir = tempDir.getRoot().toPath().resolve("test");
        Path testTxt = workDir.resolve(TEST_PATH);

        builderFactory.builder()
                .init()
                .directory(workDir)
                .workDir(workDir.getParent())
                .build()
                .run();

        //First, create a file with UTF-8 output. This just makes it easier to verify both sides of transcoding:
        // 1. Transcoding decides the file is UTF-8  (original)
        // 2. Transcoding actually rewrites the file (updated)
        Files.write(testTxt, "Test\n".getBytes(Charsets.UTF_8), StandardOpenOption.CREATE_NEW);
        builderFactory.builder().add()
                .path(TEST_PATH)
                .workDir(workDir)
                .build()
                .run();
        builderFactory.builder().commit()
                .author("Bryan Turner", "bturner@atlassian.com")
                .message("Initial commit with UTF-8 encoding")
                .workDir(workDir)
                .build()
                .run();

        //Next, rewrite the file with GB18030 text and commit that, creating a diff which switches encodings
        try (InputStream inputStream = new ClassPathResource("gb18030.txt", GitTranscodeTest.class).getInputStream();
             OutputStream outputStream = Files.newOutputStream(testTxt, StandardOpenOption.TRUNCATE_EXISTING)) {
            ByteStreams.copy(inputStream, outputStream);
        }

        builderFactory.builder().command("commit")
                .argument("-a")
                .argument("-m")
                .argument("Second commit with GB18030 encoding")
                .withEnvironment(GitConstants.ENV_AUTHOR_EMAIL, "bturner@atlassian.com")
                .withEnvironment(GitConstants.ENV_AUTHOR_NAME, "Bryan Turner")
                .withEnvironment(GitConstants.ENV_COMMITTER_EMAIL, "bturner@atlassian.com")
                .withEnvironment(GitConstants.ENV_COMMITTER_NAME, "Bryan Turner")
                .workDir(workDir)
                .build(new LoggingStdioHandler())
                .run();

        Path path = workDir.resolve(".git"); //Make the repository appear bare
        return new DefaultPathRepository(path);
    }
}
