package com.atlassian.bitbucket.mesh.git;

import com.atlassian.bitbucket.mesh.AbstractGrpcServiceTest;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderFactory;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.rpc.v1.*;
import com.atlassian.bitbucket.mesh.test.annotation.DirtiesRepo;
import com.atlassian.bitbucket.mesh.test.annotation.Repo;
import com.google.protobuf.ByteString;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Map;
import java.util.function.Function;

import static java.util.stream.Collectors.toMap;

public class GrpcRefServiceResolveTest extends AbstractGrpcServiceTest {

    private static final String BASIC_BRANCHING_NAME = "basic_branching";
    private static final RpcBranch BASIC_BRANCHING = branch(BASIC_BRANCHING_NAME,
            "d6edcbf924697ab811a867421dab60d954ccad99");
    private static final RpcBranch BRANCH_MOD_MERGE = branch("branch_mod_merge",
            "6053a1eaa1c009dd11092d09a72f3c41af1b59ad");
    private static final String MASTER_NAME = "master";
    private static final RpcBranch MASTER = branch(MASTER_NAME, "0a943a29376f2336b78312d99e65da17048951db");
    private static final String OUT_OF_ORDER_NAME = "out_of_order_branch";
    private static final RpcBranch OUT_OF_ORDER = branch(OUT_OF_ORDER_NAME,
            "2e10dd2d1d5eea9291b296e78312e8a703964a95");
    private static final String RETAGGED_SIGNED_TAG_NAME = "retagged_signed_tag";
    private static final RpcTag RETAGGED_SIGNED_TAG = tag(RETAGGED_SIGNED_TAG_NAME,
            "9b4e781dea0769888fe270e06ad76675f73851b1", "12ebe2a58367347cd39f19f5a72f3cbec7b8f9a9");
    private static final String SIGNED_TAG_NAME = "signed_tag";
    private static final RpcTag SIGNED_TAG = tag(SIGNED_TAG_NAME,
            "12ebe2a58367347cd39f19f5a72f3cbec7b8f9a9", "0a943a29376f2336b78312d99e65da17048951db");

    @Autowired
    private GitProcessBuilderFactory builderFactory;
    @Repo
    private Repository repository;

    @Test
    public void testBranches() {
        Map<String, RpcResolvedRef> refs = resolve(RpcResolveRefsRequest.newBuilder()
                .addBranchIds(MASTER.getId())
                .addBranchIds(ByteString.copyFromUtf8(BASIC_BRANCHING_NAME))
                .addBranchIds(ByteString.copyFromUtf8("no-such-branch"))
                .setRepository(repository.getId())
                .build());

        assertEquals(2, refs.size());
        assertResolvedBranch(BASIC_BRANCHING, refs.get(BASIC_BRANCHING_NAME));
        assertResolvedBranch(MASTER, refs.get(MASTER.getId().toStringUtf8()));
    }

    @Test
    public void testMixed() {
        Map<String, RpcResolvedRef> refs = resolve(RpcResolveRefsRequest.newBuilder()
                .addBranchIds(MASTER.getId())
                .addBranchIds(ByteString.copyFromUtf8(BASIC_BRANCHING_NAME))
                .addBranchIds(ByteString.copyFromUtf8("no-such-branch"))
                .addRefIds(ByteString.copyFromUtf8(OUT_OF_ORDER_NAME))
                .addRefIds(ByteString.copyFromUtf8("no-such-ref"))
                .addTagIds(SIGNED_TAG.getId())
                .addTagIds(ByteString.copyFromUtf8(RETAGGED_SIGNED_TAG_NAME))
                .addTagIds(ByteString.copyFromUtf8("no-such-tag"))
                .setRepository(repository.getId())
                .build());

        assertEquals(5, refs.size());
        assertResolvedBranch(BASIC_BRANCHING, refs.get(BASIC_BRANCHING_NAME));
        assertResolvedBranch(MASTER, refs.get(MASTER.getId().toStringUtf8()));
        assertResolvedBranch(OUT_OF_ORDER, refs.get(OUT_OF_ORDER_NAME));
        assertResolvedTag(RETAGGED_SIGNED_TAG, refs.get(RETAGGED_SIGNED_TAG_NAME));
        assertResolvedTag(SIGNED_TAG, refs.get(SIGNED_TAG.getId().toStringUtf8()));
    }

    @Test
    public void testTags() {
        Map<String, RpcResolvedRef> refs = resolve(RpcResolveRefsRequest.newBuilder()
                .addTagIds(SIGNED_TAG.getId())
                .addTagIds(ByteString.copyFromUtf8(RETAGGED_SIGNED_TAG_NAME))
                .addTagIds(ByteString.copyFromUtf8("no-such-tag"))
                .setRepository(repository.getId())
                .build());

        assertEquals(2, refs.size());
        assertResolvedTag(SIGNED_TAG, refs.get(SIGNED_TAG.getId().toStringUtf8()));
        assertResolvedTag(RETAGGED_SIGNED_TAG, refs.get(RETAGGED_SIGNED_TAG_NAME));
    }

    @DirtiesRepo
    @Test
    public void testWithSha() {
        // create two branches:
        // - firstBranch (000aaa) which should be returned first because of alpha ordering
        // - problemBranch (57150c54c3) whose branch-name matches the hash of firstBranch
        // when we try to resolve "57150c54c3", we should get problemBranch and _not_ firstBranch
        RpcBranch firstBranch = branch("000aaa", "57150c54c38d6570b2fd5e6d6cfc19476de44e84");
        String problemBranchName = firstBranch.getLatestCommit().substring(0, 10);
        RpcBranch problemBranch = branch(problemBranchName, "e26cab95a50f6e99d2c46c498a23cee307cc8e27");
        createBranch(firstBranch);
        createBranch(problemBranch);

        Map<String, RpcResolvedRef> refs = resolve(RpcResolveRefsRequest.newBuilder()
                .addBranchIds(ByteString.copyFromUtf8("heads/master"))
                .addBranchIds(ByteString.copyFromUtf8(BASIC_BRANCHING_NAME))
                .addBranchIds(ByteString.copyFromUtf8("no-such-branch"))
                .addRefIds(ByteString.copyFromUtf8(OUT_OF_ORDER_NAME))
                .addRefIds(ByteString.copyFromUtf8("no-such-ref"))
                .addRefIds(ByteString.copyFromUtf8("6053a1eaa"))
                .addRefIds(ByteString.copyFromUtf8(problemBranchName))
                .addTagIds(SIGNED_TAG.getId())
                .addTagIds(ByteString.copyFromUtf8(RETAGGED_SIGNED_TAG_NAME))
                .addTagIds(ByteString.copyFromUtf8("no-such-tag"))
                .setRepository(repository.getId())
                .build());

        assertResolvedBranch(BASIC_BRANCHING, refs.get(BASIC_BRANCHING_NAME));
        assertResolvedBranch(MASTER, refs.get("heads/master"));
        assertResolvedTag(RETAGGED_SIGNED_TAG, refs.get(RETAGGED_SIGNED_TAG_NAME));
        assertResolvedBranch(OUT_OF_ORDER, refs.get(OUT_OF_ORDER_NAME));
        assertResolvedTag(SIGNED_TAG, refs.get(SIGNED_TAG.getId().toStringUtf8()));
        assertResolvedBranch(BRANCH_MOD_MERGE, refs.get("6053a1eaa"));
        // problem branch should be returned _instead_ of firstBranch
        assertResolvedBranch(problemBranch, refs.get(problemBranchName));
        assertEquals(7, refs.size());

    }

    private static void assertResolvedBranch(RpcBranch expected, RpcResolvedRef ref) {
        assertNotNull(expected.getId().toStringUtf8() + " was not resolved", ref);
        assertTrue(ref.hasBranch());
        assertEquals(expected, ref.getBranch());
    }

    private static void assertResolvedTag(RpcTag expected, RpcResolvedRef ref) {
        assertNotNull(ref);
        assertTrue(ref.hasTag());
        assertEquals(expected, ref.getTag());
    }

    private static RpcBranch branch(String name, String commitId) {
        return branch(name, commitId, "master".equals(name));
    }

    private static RpcBranch branch(String name, String commitId, boolean isDefault) {
        return RpcBranch.newBuilder()
                .setId(ByteString.copyFromUtf8(GitRefPattern.HEADS.qualify(name)))
                .setLatestCommit(commitId)
                .setDefault(isDefault)
                .build();
    }

    private static RpcTag tag(String name, String hash, String commitId) {
        return RpcTag.newBuilder()
                .setId(ByteString.copyFromUtf8(GitRefPattern.TAGS.qualify(name)))
                .setHash(hash)
                .setLatestCommit(commitId)
                .build();
    }

    private void createBranch(RpcBranch branch) {
        builderFactory.builder(repository).updateRef()
                .set(branch.getId().toStringUtf8(), branch.getLatestCommit())
                .build()
                .run();
    }

    private Map<String, RpcResolvedRef> resolve(RpcResolveRefsRequest request) {
        return stub().resolveRefs(request).getResolvedRefsList().stream()
                .collect(toMap(resolvedRef -> resolvedRef.getRefId().toStringUtf8(), Function.identity()));
    }

    private RefServiceGrpc.RefServiceBlockingStub stub() {
        return RefServiceGrpc.newBlockingStub(managedChannel);
    }
}
