package com.atlassian.stash.internal.scm.git.transcode;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.CommandOutputHandler;
import com.atlassian.bitbucket.scm.git.GitAgent;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderSupport;
import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder;
import com.atlassian.bitbucket.scm.git.command.LoggingCommandOutputHandler;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.stash.internal.scm.git.AbstractGitTest;
import com.atlassian.stash.internal.scm.git.util.ConfigurableGitScmConfig;
import com.atlassian.utils.process.StringOutputHandler;
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.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
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 AbstractGitTest {

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

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

    @Autowired
    private GitCommandBuilderFactory builderFactory;
    @Autowired
    private ConfigurableGitScmConfig config;
    private ExecutorService executorService;
    @Autowired
    private I18nService i18nService;
    @Autowired
    private ApplicationPropertiesService propertiesService;
    private Repository repository;
    private DefaultTranscodeServer server;
    private TranscodeService service;
    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(propertiesService));

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

        service = new DefaultTranscodeService(builderFactory, config, i18nService);
    }

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

    /**
     *
     * @throws Exception
     */
    @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));
        verifyZeroInteractions(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(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(diff.contains(DefaultTranscoderTest.ACCENTS));
        verifyZeroInteractions(transcoder);
    }

    private String runDiff() {
        GitScmCommandBuilder builder = builderFactory.builder(repository);
        new TranscodeGitCommandBuilderConfigurer(server)
                .configure(builder);

        String diff = builder.diff()
                .ancestor(GitAgent.HEAD + "^")
                .rev(GitAgent.HEAD)
                .path(TEST_PATH)
                .build(new StringCommandOutputHandler())
                .call();
        assertNotNull(diff);

        return diff;
    }

    private Repository buildRepository() throws IOException {
        File workDir = new File(folder.getRoot(), "test");
        final Path testTxt = Paths.get(workDir.getAbsolutePath(), TEST_PATH);

        builderFactory.builder()
                .init()
                .directory(workDir.getName())
                .workingDirectory(workDir.getParentFile())
                .build()
                .call();

        //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)
                .workingDirectory(workDir)
                .build()
                .call();
        builderFactory.builder().commit()
                .author("Bryan Turner", "bturner@atlassian.com")
                .message("Initial commit with UTF-8 encoding")
                .workingDirectory(workDir)
                .build()
                .call();

        //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);
        }

        LoggingCommandOutputHandler.call(builderFactory.builder()
                .command("commit")
                .argument("-a")
                .argument("-m")
                .argument("Second commit with GB18030 encoding")
                .withEnvironment(GitCommandBuilderSupport.ENV_AUTHOR_EMAIL, "bturner@atlassian.com")
                .withEnvironment(GitCommandBuilderSupport.ENV_AUTHOR_NAME, "Bryan Turner")
                .withEnvironment(GitCommandBuilderSupport.ENV_COMMITTER_EMAIL, "bturner@atlassian.com")
                .withEnvironment(GitCommandBuilderSupport.ENV_COMMITTER_NAME, "Bryan Turner")
                .workingDirectory(workDir));

        //If we make it here, store the repository's location
        Repository repository = mock(Repository.class);
        config.setRepositoryDir(repository, new File(workDir, ".git")); //Make the repository appear bare

        return repository;
    }

    private static class StringCommandOutputHandler extends StringOutputHandler implements CommandOutputHandler<String> {

        private StringCommandOutputHandler() {
            super("UTF-8");
        }
    }
}
