package com.atlassian.bitbucket.mesh.repair;

import com.atlassian.bitbucket.mesh.git.ref.Branch;
import com.atlassian.bitbucket.mesh.git.ref.Ref;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Test;

import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;

import static org.junit.Assert.assertEquals;

public class DifferTest {

    @Test(timeout = 1000L)
    public void testEmptyFromAndTo() {
        Map<String, String> content = ImmutableMap.of();
        diffAndVerify(content, content, ImmutableMap.of());
    }

    @Test(timeout = 1000L)
    public void testIdenticalFromAndTo() {
        Map<String, String> content = ImmutableMap.of(
                "key", "value",
                "key2", "value2");
        diffAndVerify(content, content, ImmutableMap.of());
    }

    @Test(timeout = 1000L)
    public void testMixed() {
        diffAndVerify(
                ImmutableMap.of(
                        "only-in-from", "value",
                        "shared-and-equal", "shared-value",
                        "shared-but-different", "from-value"),
                ImmutableMap.of(
                        "only-in-to", "value",
                        "shared-and-equal", "shared-value",
                        "shared-but-different", "to-value"),
                ImmutableMap.of(
                        "only-in-from", Pair.of("value", null),
                        "only-in-to", Pair.of(null, "value"),
                        "shared-but-different", Pair.of("from-value", "to-value"))
        );
    }

    @Test(timeout = 1000L)
    public void testMixedReversed() {
        diffAndVerify(
                ImmutableMap.of(
                        "shared-and-equal", "shared-value",
                        "shared-but-different", "from-value",
                        "z-only-in-from", "value"),
                ImmutableMap.of(
                        "shared-and-equal", "shared-value",
                        "shared-but-different", "to-value",
                        "z-only-in-to", "value"),
                ImmutableMap.of(
                        "shared-but-different", Pair.of("from-value", "to-value"),
                        "z-only-in-from", Pair.of("value", null),
                        "z-only-in-to", Pair.of(null, "value"))
        );
    }

    @Test(timeout = 1000L)
    public void testFromEmpty() {
        diffAndVerify(
                ImmutableMap.of(),
                ImmutableMap.of(
                        "key", "value",
                        "key2", "value2"),
                ImmutableMap.of(
                        "key", Pair.of(null, "value"),
                        "key2", Pair.of(null, "value2"))
        );
    }

    @Test(timeout = 1000L)
    public void testToEmpty() {
        diffAndVerify(
                ImmutableMap.of(
                        "key", "value",
                        "key2", "value2"),
                ImmutableMap.of(),
                ImmutableMap.of(
                        "key", Pair.of("value", null),
                        "key2", Pair.of("value2", null))
        );
    }

    private static void diffAndVerify(Map<String, String> fromValues, Map<String, String> toValues,
                                      Map<String, Pair<String, String>> expectedDiffs) {
        ImmutableMap.Builder<String, Pair<String, String>> builder = ImmutableMap.builder();
        Differ<Pair<String, String>> differ = new Differ<>(
                Comparator.comparing(Pair::getKey),
                Objects::equals,
                (from, to) -> {
                    String key = from == null ? to.getKey() : from.getKey();
                    String fromValue = from == null ? null : from.getValue();
                    String toValue = to == null ? null : to.getValue();
                    builder.put(key, Pair.of(fromValue, toValue));
                }
        );
        ForkJoinTask<Void> fromFuture = ForkJoinPool.commonPool().submit(() -> {
            Differ.Sequence<Pair<String, String>> from = differ.from();
            fromValues.forEach((key, value) -> from.onNext(Pair.of(key, value)));
            from.close();
            return null;
        });
        ForkJoinTask<Void> toFuture = ForkJoinPool.commonPool().submit(() -> {
            Differ.Sequence<Pair<String, String>> to = differ.to();
            toValues.forEach((key, value) -> to.onNext(Pair.of(key, value)));
            to.close();
            return null;
        });

        fromFuture.join();
        toFuture.join();
        assertEquals(expectedDiffs, builder.build());
    }

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