package com.atlassian.bitbucket.mesh.repair;

import com.atlassian.bitbucket.mesh.git.ref.Branch;
import com.atlassian.bitbucket.mesh.git.ref.ProposedRefUpdate;
import com.atlassian.bitbucket.mesh.git.ref.Ref;
import com.google.common.collect.ImmutableList;
import org.junit.Test;

import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;

import static org.junit.Assert.*;

public class RefDifferTest {

    @Test(timeout = 1000L)
    public void testIdenticalRefsResultsInNoUpdates() {
        List<Ref> refs = ImmutableList.of(
                ref("refs/heads/master", "ac4ada5e49450d75f278c431f6f9f076a9130537"),
                ref("refs/tags/release/1.1", "7fce5067527967bb421e3e7e035aac47ca7e56dc"));
        diffAndVerify(refs, refs, ImmutableList.of());
    }

    @Test(timeout = 1000L)
    public void testMixed() {
        diffAndVerify(
                ImmutableList.of(
                        ref("refs/heads/master", "494ada5e49450d75f278c431f6f9f076a913053c"), //identical
                        ref("refs/heads/ppppp", "e840e35b474fdb80104c224a3fb9c7fb1fa3766d"), //missing in to
                        ref("refs/heads/release/1", "8c2223f501ddbf7bf1ccb5a4daab39fb8c20b94f"), //different
                        ref("refs/heads/release/2", "36b10e4b7b5bf5fdef11af84af11de09bc229376"), //different
                        ref("refs/heads/release/3", "a592af0f29b0a1ad61ba77822eacb2b9b2d93f87") //identical
                ),
                ImmutableList.of(
                        ref("refs/heads/aaaa", "19568126fa602e7db0da2da87a4600b29150aa44"), //missing in from
                        ref("refs/heads/aaab", "6c8f4598893d673e2947b03563122245d9660190"), //missing in from
                        ref("refs/heads/master", "494ada5e49450d75f278c431f6f9f076a913053c"), //identical
                        ref("refs/heads/release/1", "e7eda61f81502f33a25f0e4fd9b24b3cd0a9ada4"), //different
                        ref("refs/heads/release/2", "85dfd772efcff48355977021098574038df40512"), //different
                        ref("refs/heads/release/3", "a592af0f29b0a1ad61ba77822eacb2b9b2d93f87"), //identical
                        ref("refs/heads/zzzx", "eed9ca11bd728ceab45c8f461135a3cd59888ecf"), //missing in from
                        ref("refs/heads/zzzy", "9748f8a80f1d7349fdb81a0e575fe00e4e8f4704"), //missing in from
                        ref("refs/heads/zzzz", "92c1891f5dfb7847c200f38cf5d549a6656c3d97") //missing in from
                ),
                ImmutableList.of(
                        ProposedRefUpdate.toAdd("refs/heads/aaaa", "19568126fa602e7db0da2da87a4600b29150aa44"),
                        ProposedRefUpdate.toAdd("refs/heads/aaab", "6c8f4598893d673e2947b03563122245d9660190"),
                        ProposedRefUpdate.toDelete("refs/heads/ppppp", "e840e35b474fdb80104c224a3fb9c7fb1fa3766d"),
                        ProposedRefUpdate.of("refs/heads/release/1",
                                "8c2223f501ddbf7bf1ccb5a4daab39fb8c20b94f", "e7eda61f81502f33a25f0e4fd9b24b3cd0a9ada4"),
                        ProposedRefUpdate.of("refs/heads/release/2",
                                "36b10e4b7b5bf5fdef11af84af11de09bc229376", "85dfd772efcff48355977021098574038df40512"),
                        ProposedRefUpdate.toAdd("refs/heads/zzzx", "eed9ca11bd728ceab45c8f461135a3cd59888ecf"),
                        ProposedRefUpdate.toAdd("refs/heads/zzzy", "9748f8a80f1d7349fdb81a0e575fe00e4e8f4704"),
                        ProposedRefUpdate.toAdd("refs/heads/zzzz", "92c1891f5dfb7847c200f38cf5d549a6656c3d97"))
        );
    }

    @Test(timeout = 1000L)
    public void testMixedInverse() {
        //Same scenario as testMixed, but with from and to flipped
        diffAndVerify(
                ImmutableList.of(
                        ref("refs/heads/aaaa", "19568126fa602e7db0da2da87a4600b29150aa44"), //missing in to
                        ref("refs/heads/aaab", "6c8f4598893d673e2947b03563122245d9660190"), //missing in to
                        ref("refs/heads/master", "494ada5e49450d75f278c431f6f9f076a913053c"), //identical
                        ref("refs/heads/release/1", "e7eda61f81502f33a25f0e4fd9b24b3cd0a9ada4"), //different
                        ref("refs/heads/release/2", "85dfd772efcff48355977021098574038df40512"), //different
                        ref("refs/heads/release/3", "a592af0f29b0a1ad61ba77822eacb2b9b2d93f87"), //identical
                        ref("refs/heads/zzzx", "eed9ca11bd728ceab45c8f461135a3cd59888ecf"), //missing in to
                        ref("refs/heads/zzzy", "9748f8a80f1d7349fdb81a0e575fe00e4e8f4704"), //missing in to
                        ref("refs/heads/zzzz", "92c1891f5dfb7847c200f38cf5d549a6656c3d97") //missing in to
                ),
                ImmutableList.of(
                        ref("refs/heads/master", "494ada5e49450d75f278c431f6f9f076a913053c"), //identical
                        ref("refs/heads/ppppp", "e840e35b474fdb80104c224a3fb9c7fb1fa3766d"), //missing in from
                        ref("refs/heads/release/1", "8c2223f501ddbf7bf1ccb5a4daab39fb8c20b94f"), //different
                        ref("refs/heads/release/2", "36b10e4b7b5bf5fdef11af84af11de09bc229376"), //different
                        ref("refs/heads/release/3", "a592af0f29b0a1ad61ba77822eacb2b9b2d93f87") //identical
                ),
                ImmutableList.of(
                        ProposedRefUpdate.toDelete("refs/heads/aaaa", "19568126fa602e7db0da2da87a4600b29150aa44"),
                        ProposedRefUpdate.toDelete("refs/heads/aaab", "6c8f4598893d673e2947b03563122245d9660190"),
                        ProposedRefUpdate.toAdd("refs/heads/ppppp", "e840e35b474fdb80104c224a3fb9c7fb1fa3766d"),
                        ProposedRefUpdate.of("refs/heads/release/1",
                                "e7eda61f81502f33a25f0e4fd9b24b3cd0a9ada4", "8c2223f501ddbf7bf1ccb5a4daab39fb8c20b94f"),
                        ProposedRefUpdate.of("refs/heads/release/2",
                                "85dfd772efcff48355977021098574038df40512", "36b10e4b7b5bf5fdef11af84af11de09bc229376"),
                        ProposedRefUpdate.toDelete("refs/heads/zzzx", "eed9ca11bd728ceab45c8f461135a3cd59888ecf"),
                        ProposedRefUpdate.toDelete("refs/heads/zzzy", "9748f8a80f1d7349fdb81a0e575fe00e4e8f4704"),
                        ProposedRefUpdate.toDelete("refs/heads/zzzz", "92c1891f5dfb7847c200f38cf5d549a6656c3d97"))
        );
    }

    @Test(timeout = 1000L)
    public void testSourceEmptyDeletesAllRefs() {
        diffAndVerify(
                ImmutableList.of(
                        ref("refs/heads/master", "ac4ada5e49450d75f278c431f6f9f076a9130537"),
                        ref("refs/tags/release/1.1", "7fce5067527967bb421e3e7e035aac47ca7e56dc")),
                ImmutableList.of(),
                ImmutableList.of(
                        ProposedRefUpdate.toDelete("refs/heads/master", "ac4ada5e49450d75f278c431f6f9f076a9130537"),
                        ProposedRefUpdate.toDelete("refs/tags/release/1.1", "7fce5067527967bb421e3e7e035aac47ca7e56dc")
                ));
    }

    @Test(timeout = 1000L)
    public void testTargetEmptyAddsAllRefs() {
        diffAndVerify(
                ImmutableList.of(),
                ImmutableList.of(
                        ref("refs/heads/master", "ac4ada5e49450d75f278c431f6f9f076a9130537"),
                        ref("refs/tags/release/1.1", "7fce5067527967bb421e3e7e035aac47ca7e56dc")),
                ImmutableList.of(
                        ProposedRefUpdate.toAdd("refs/heads/master", "ac4ada5e49450d75f278c431f6f9f076a9130537"),
                        ProposedRefUpdate.toAdd("refs/tags/release/1.1", "7fce5067527967bb421e3e7e035aac47ca7e56dc")
                ));
    }
    
    private static void diffAndVerify(List<Ref> fromRefs, List<Ref> toRefs, List<ProposedRefUpdate> expectedUpdates) {
        ImmutableList.Builder<ProposedRefUpdate> builder = ImmutableList.builder();
        RefDiffer differ = new RefDiffer(builder::add);
        ForkJoinTask<Void> fromFuture = ForkJoinPool.commonPool().submit(() -> {
            Differ.Sequence<Ref> from = differ.from();
            fromRefs.forEach(from::onNext);
            from.close();
            return null;
        });
        ForkJoinTask<Void> toFuture = ForkJoinPool.commonPool().submit(() -> {
            Differ.Sequence<Ref> to = differ.to();
            toRefs.forEach(to::onNext);
            to.close();
            return null;
        });

        fromFuture.join();
        toFuture.join();
        List<ProposedRefUpdate> actualUpdates = builder.build();
        assertEquals(expectedUpdates, actualUpdates);
    }

    private static Ref ref(String refId, String hash) {
        return new Branch.Builder()
                .id(refId)
                .latestCommit(hash)
                .build();
    }
}