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

import com.atlassian.bitbucket.mesh.AbstractWiredTest;
import com.atlassian.bitbucket.mesh.git.GitAgent;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderFactory;
import com.atlassian.bitbucket.mesh.git.builder.updateref.UpdateRefStdioHandler;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
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.ImmutableMap;
import org.junit.Test;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.file.Path;
import java.util.stream.Stream;

public class RefStreamerTest extends AbstractWiredTest {

    @Resource
    private GitAgent agent;
    @Resource
    private GitProcessBuilderFactory builderFactory;
    @Repo
    private Repository repository;

    @DirtiesRepo
    @Test
    public void testStreamRefs() throws IOException {
        builderFactory.builder(repository).updateRef()
                .stdin()
                .nullTerminated(true)
                .withConfiguration("core.hooksPath", "") //don't try to coordinate with replicas
                .build(new UpdateRefStdioHandler(Stream.of(
                        //These are intentionally out of order to verify that streamRefs streams in order
                        "create dark-refs/heads/pull/1/from" + '\0' + "042fb0cc198e7320b59a025689b45a2cb52f4684",
                        "create dark-refs/heads/pull/87/from" + '\0' + "8fb8538799b2481750eac52716931d895d029b7c",
                        "create dark-refs/heads/pull/70/from" + '\0' + "53979b3304bece75a3852a3d250dbaed64d45430",
                        "create dark-refs/heads/c/branch-3" + '\0' + "2d8897c9ac29ce42c3442cf80ac977057045e7f6",
                        "create dark-refs/heads/pull/63/from" + '\0' + "4f9290ae1a9fcde7acd56664a22cc65eb76540f3",
                        "create dark-refs/heads/pull/63/to" + '\0' + "2e10dd2d1d5eea9291b296e78312e8a703964a95",
                        "create stash-refs/pull-requests/87/from" + '\0' + "8fb8538799b2481750eac52716931d895d029b7c",
                        "create stash-refs/pull-requests/70/from" + '\0' + "53979b3304bece75a3852a3d250dbaed64d45430",
                        "create stash-refs/pull-requests/63/from" + '\0' + "4f9290ae1a9fcde7acd56664a22cc65eb76540f3",
                        "create stash-refs/pull-requests/63/to" + '\0' + "2e10dd2d1d5eea9291b296e78312e8a703964a95"),
                        true))
                .run();
        //Add a symbolic ref that should be ignored
        Path symbolicRef = repository.getPath().resolve("dark-refs/pull/99/from");
        MoreFiles.mkdir(symbolicRef.getParent());
        MoreFiles.write(symbolicRef, "ref: refs/heads/master");

        //Add a non-ref file, which should also be ignored
        Path nonRef = repository.getPath().resolve("dark-refs/pull/98/from");
        MoreFiles.mkdir(nonRef.getParent());
        MoreFiles.write(nonRef, "this is not a ref");

        //Dark and public refs
        doTestStreamRefs(true, true, true, ImmutableMap.<String, String>builder()
                .put("refs/heads/basic_branching", "d6edcbf924697ab811a867421dab60d954ccad99")
                .put("refs/heads/branch_mod_merge", "6053a1eaa1c009dd11092d09a72f3c41af1b59ad")
                .put("refs/heads/master", "0a943a29376f2336b78312d99e65da17048951db")
                .put("refs/heads/out_of_order_branch", "2e10dd2d1d5eea9291b296e78312e8a703964a95")
                .put("refs/tags/backdated_annotated_tag", "57150c54c38d6570b2fd5e6d6cfc19476de44e84")
                .put("refs/tags/retagged_signed_tag", "12ebe2a58367347cd39f19f5a72f3cbec7b8f9a9")
                .put("refs/tags/signed_tag", "0a943a29376f2336b78312d99e65da17048951db")
                .put("dark-refs/heads/pull/1/from", "042fb0cc198e7320b59a025689b45a2cb52f4684")
                .put("dark-refs/heads/pull/63/from", "4f9290ae1a9fcde7acd56664a22cc65eb76540f3")
                .put("dark-refs/heads/pull/70/from", "53979b3304bece75a3852a3d250dbaed64d45430")
                .put("dark-refs/heads/pull/87/from", "8fb8538799b2481750eac52716931d895d029b7c")
                .put("stash-refs/pull-requests/63/from", "4f9290ae1a9fcde7acd56664a22cc65eb76540f3")
                .put("stash-refs/pull-requests/70/from", "53979b3304bece75a3852a3d250dbaed64d45430")
                .put("stash-refs/pull-requests/87/from", "8fb8538799b2481750eac52716931d895d029b7c")
                .build());

        //Dark refs only
        doTestStreamRefs(false, true, false, ImmutableMap.<String, String>builder()
                .put("dark-refs/heads/pull/1/from", "042fb0cc198e7320b59a025689b45a2cb52f4684")
                .put("dark-refs/heads/pull/63/from", "4f9290ae1a9fcde7acd56664a22cc65eb76540f3")
                .put("dark-refs/heads/pull/70/from", "53979b3304bece75a3852a3d250dbaed64d45430")
                .put("dark-refs/heads/pull/87/from", "8fb8538799b2481750eac52716931d895d029b7c")
                .build());

        //Public refs only
        doTestStreamRefs(true, false, false, ImmutableMap.<String, String>builder()
                .put("refs/heads/basic_branching", "d6edcbf924697ab811a867421dab60d954ccad99")
                .put("refs/heads/branch_mod_merge", "6053a1eaa1c009dd11092d09a72f3c41af1b59ad")
                .put("refs/heads/master", "0a943a29376f2336b78312d99e65da17048951db")
                .put("refs/heads/out_of_order_branch", "2e10dd2d1d5eea9291b296e78312e8a703964a95")
                .put("refs/tags/backdated_annotated_tag", "57150c54c38d6570b2fd5e6d6cfc19476de44e84")
                .put("refs/tags/retagged_signed_tag", "12ebe2a58367347cd39f19f5a72f3cbec7b8f9a9")
                .put("refs/tags/signed_tag", "0a943a29376f2336b78312d99e65da17048951db")
                .build());

        //Stash refs only
        doTestStreamRefs(false, false, true, ImmutableMap.<String, String>builder()
                .put("stash-refs/pull-requests/63/from", "4f9290ae1a9fcde7acd56664a22cc65eb76540f3")
                .put("stash-refs/pull-requests/70/from", "53979b3304bece75a3852a3d250dbaed64d45430")
                .put("stash-refs/pull-requests/87/from", "8fb8538799b2481750eac52716931d895d029b7c")
                .build());
    }

    @Test
    public void testStreamRefsForRepoWithoutDarkAndStashRefs() throws IOException {
        doTestStreamRefs(true, true, true, ImmutableMap.<String, String>builder()
                .put("refs/heads/basic_branching", "d6edcbf924697ab811a867421dab60d954ccad99")
                .put("refs/heads/branch_mod_merge", "6053a1eaa1c009dd11092d09a72f3c41af1b59ad")
                .put("refs/heads/master", "0a943a29376f2336b78312d99e65da17048951db")
                .put("refs/heads/out_of_order_branch", "2e10dd2d1d5eea9291b296e78312e8a703964a95")
                .put("refs/tags/backdated_annotated_tag", "57150c54c38d6570b2fd5e6d6cfc19476de44e84")
                .put("refs/tags/retagged_signed_tag", "12ebe2a58367347cd39f19f5a72f3cbec7b8f9a9")
                .put("refs/tags/signed_tag", "0a943a29376f2336b78312d99e65da17048951db")
                .build());
    }

    private void doTestStreamRefs(boolean publicRefs, boolean darkRefs,
                                  boolean stashRefs, ImmutableMap<String, String> expected) throws IOException {
        ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
        RefStreamer advertiser = new RefStreamer(agent, builderFactory);

        //Stream the refs
        advertiser.streamRefs(new StreamRefsParameters.Builder(repository)
                .darkRefs(darkRefs)
                .publicRefs(publicRefs)
                .stashRefs(stashRefs)
                .build(), ref -> {
            builder.put(ref.getId(), ref.getLatestCommit());
            return false;
        });

        ImmutableMap<String, String> actual = builder.build();

        //Validate that all expected refs were streamed
        assertEquals(expected, actual);

        //Validate that the refs were streamed in the correct order
        assertEquals(expected.keySet().asList(), actual.keySet().asList());
    }
}