package com.atlassian.bitbucket.mesh.git;

import com.atlassian.bitbucket.mesh.AbstractGrpcServiceTest;
import com.atlassian.bitbucket.mesh.git.ref.RefUpdate;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.repository.BitbucketServerRepository;
import com.atlassian.bitbucket.mesh.repository.ManagedRepository;
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.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.protobuf.ByteString;
import org.junit.Test;

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;

import static com.atlassian.bitbucket.mesh.git.GitConstants.*;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;

public class GrpcPullRequestServiceTest extends AbstractGrpcServiceTest {

    @Repo
    private BitbucketServerRepository bitbucketServerRepository;
    @Repo
    private ManagedRepository repository;

    @Test
    public void testBatchRescope() {
        //This tests a simple single rescope for an intra-repository pull request. The goal here is to smoke test the
        //integration from the GrpcPullRequestService down. The rescope analysis is tested separately in
        //BatchRescopeOperationTest and further down in BatchPullRequestRescopeAnalyzerTest et al
        List<RpcBatchRescopePullRequestResponseFragment> fragments = ImmutableList.copyOf(
                stub().batchRescope(RpcBatchRescopePullRequestRequest.newBuilder()
                        .addRescopes(RpcPullRequestRescope.newBuilder()
                                .setFromRepository(repository.getId())
                                .setToRepository(repository.getId())
                                .setNewFromCommit("9c05f43f859375e392d90d23a13717c16d0fdcda")
                                .setNewToCommit("d6edcbf924697ab811a867421dab60d954ccad99")
                                .setOldFromCommit("042fb0cc198e7320b59a025689b45a2cb52f4684")
                                .setOldToCommit("d6edcbf924697ab811a867421dab60d954ccad99")
                                .setRescopeId(123L)
                                .build())
                        .setMaxCommitsPerUpdate(5)
                        .setMaxMessageLength(16384)
                        .setRepository(repository.getId())
                        .build()));

        assertEquals(1, fragments.size());
        RpcBatchRescopePullRequestResponseFragment fragment = fragments.get(0);
        assertEquals(1, fragment.getOutcomesCount());

        RpcPullRequestRescopeOutcome outcome = fragment.getOutcomes(0);
        assertEquals(123L, outcome.getRescopeId());
        assertTrue(outcome.hasUpdated());

        RpcPullRequestRescopeOutcome.Updated update = outcome.getUpdated();
        assertEquals(2, update.getAddedCommitsCount());
        List<String> expectedAddedCommits = ImmutableList.of(
                "9c05f43f859375e392d90d23a13717c16d0fdcda", "6053a1eaa1c009dd11092d09a72f3c41af1b59ad");
        List<String> actualAddedCommits = update.getAddedCommitsList().stream()
                .map(RpcCommit::getId)
                .collect(Collectors.toList());

        assertEquals(expectedAddedCommits, actualAddedCommits);
    }

    @DirtiesRepo("bitbucketServerRepository")
    @Test
    public void testDeleteBitbucketServerPullRequest() throws IOException {
        doTestDeletePullRequest(bitbucketServerRepository);
    }

    @DirtiesRepo("repository")
    @Test
    public void testDeletePullRequest() throws IOException {
        doTestDeletePullRequest(repository);
    }

    @DirtiesRepo("bitbucketServerRepository")
    @Test
    public void testUpdateBitbucketServerPullRequestRefs() throws IOException {
        doTestUpdatePullRequestRefs(bitbucketServerRepository);
    }

    @DirtiesRepo("repository")
    @Test
    public void testUpdatePullRequestRefs() throws IOException {
        doTestUpdatePullRequestRefs(repository);
    }

    private void doTestDeletePullRequest(Repository repository) throws IOException {
        createPrRefsAndReflogs(repository, 11);

        RpcDeletePullRequestResponse response = stub().delete(RpcDeletePullRequestRequest.newBuilder()
                .setPullRequest(RpcPullRequest.newBuilder()
                        .setId(11L)
                        .setState(RpcPullRequestState.PR_STATE_OPEN)
                        .setFromRef(RpcPullRequestRef.newBuilder()
                                .setId(ByteString.copyFromUtf8("refs/heads/branch_mod_merge"))
                                .setLatestCommit("042fb0cc198e7320b59a025689b45a2cb52f4684")
                                .setRepository(repository.getId()))
                        .setToRef(RpcPullRequestRef.newBuilder()
                                .setId(ByteString.copyFromUtf8("refs/heads/master"))
                                .setLatestCommit("0a943a29376f2336b78312d99e65da17048951db")
                                .setRepository(repository.getId()))
                        .setVersion(3))
                .build());

        assertNotNull(response);
        assertEquals(ImmutableSet.of("refs/pull-requests/11/from"), response.getRefUpdatesList().stream()
                .map(RpcRefUpdate::getRefId)
                .map(ByteString::toStringUtf8)
                .collect(Collectors.toSet()));
        assertPrRefsDeleted(repository, 11);
    }

    private void doTestUpdatePullRequestRefs(Repository repository) throws IOException {
        //Creates the refs/pull-requests/11/from ref at 042fb0cc198e7320b59a025689b45a2cb52f4684
        createPrRefsAndReflogs(repository, 11);

        //Simulate an update of the from-ref to 6053a1eaa1c009dd11092d09a72f3c41af1b59ad
        RpcUpdatePullRequestRefsResponse response = stub().updateRefs(RpcUpdatePullRequestRefsRequest.newBuilder()
                .setPullRequest(RpcPullRequest.newBuilder()
                        .setId(11L)
                        .setState(RpcPullRequestState.PR_STATE_OPEN)
                        .setFromRef(RpcPullRequestRef.newBuilder()
                                .setId(ByteString.copyFromUtf8("refs/heads/branch_mod_merge"))
                                .setLatestCommit("6053a1eaa1c009dd11092d09a72f3c41af1b59ad")
                                .setRepository(repository.getId()))
                        .setToRef(RpcPullRequestRef.newBuilder()
                                .setId(ByteString.copyFromUtf8("refs/heads/master"))
                                .setLatestCommit("0a943a29376f2336b78312d99e65da17048951db")
                                .setRepository(repository.getId()))
                        .setVersion(3))
                .build());

        assertNotNull(response);
        assertThat(response.getRefUpdatesList(), contains(RefUpdate.of("refs/pull-requests/11/from",
                "042fb0cc198e7320b59a025689b45a2cb52f4684", "6053a1eaa1c009dd11092d09a72f3c41af1b59ad").toRefUpdate()));

        assertEquals("6053a1eaa1c009dd11092d09a72f3c41af1b59ad\n", getDarkFromRefContents(repository, 11L));
        assertEquals("6053a1eaa1c009dd11092d09a72f3c41af1b59ad\n", getPublicFromRefContents(repository, 11L));
        assertThat(getDarkFromReflogContents(repository, 11L),
                containsString("6053a1eaa1c009dd11092d09a72f3c41af1b59ad"));
    }

    private static void assertPrRefsDeleted(Repository repository, int prId) {
        String id = Integer.toString(prId);
        String darkRefsPrefix = getDarkRefPrefix(repository);

        assertFalse("Dark PR reflog dir was not deleted", MoreFiles.dirExists(
                MoreFiles.resolve(repository.getPath(), PATH_LOGS, darkRefsPrefix, PATH_PULL_REQUESTS, id)));
        assertFalse("Public PR ref dir was not deleted", MoreFiles.dirExists(
                MoreFiles.resolve(repository.getPath(), PATH_REFS, PATH_PULL_REQUESTS, id)));
        assertFalse("Dark PR ref dir was not deleted", MoreFiles.dirExists(
                MoreFiles.resolve(repository.getPath(), darkRefsPrefix, PATH_PULL_REQUESTS, id)));
    }

    private static void createPrRefsAndReflogs(Repository repository, long prId) throws IOException {
        String id = Long.toString(prId);
        String darkRefsPrefix = getDarkRefPrefix(repository);
        //Create a reflog file
        Path reflogDir = MoreFiles.mkdir(MoreFiles.resolve(
                repository.getPath(), PATH_LOGS, darkRefsPrefix, PATH_PULL_REQUESTS, id));
        MoreFiles.touch(reflogDir.resolve("from"));

        //Create the public ref
        Path refDir = MoreFiles.mkdir(MoreFiles.resolve(
                repository.getPath(), PATH_REFS, PATH_PULL_REQUESTS, id));
        MoreFiles.write(refDir.resolve("from"), "042fb0cc198e7320b59a025689b45a2cb52f4684");
        //Create the dark ref
        Path darkRefDir = MoreFiles.mkdir(MoreFiles.resolve(
                repository.getPath(), darkRefsPrefix, PATH_PULL_REQUESTS, id));
        MoreFiles.write(darkRefDir.resolve("from"), "042fb0cc198e7320b59a025689b45a2cb52f4684");
    }

    private static String getDarkFromRefContents(Repository repository, long prId) throws IOException {
        Path path = MoreFiles.resolve(repository.getPath(), getDarkRefPrefix(repository), PATH_PULL_REQUESTS,
                Long.toString(prId), "from");
        return MoreFiles.toString(path, UTF_8);
    }

    private static String getDarkFromReflogContents(Repository repository, long prId) throws IOException {
        Path path = MoreFiles.resolve(repository.getPath(), PATH_LOGS, getDarkRefPrefix(repository),
                PATH_PULL_REQUESTS, Long.toString(prId), "from");
        return MoreFiles.toString(path, UTF_8);
    }

    private static String getDarkRefPrefix(Repository repository) {
        return repository instanceof BitbucketServerRepository ? PATH_STASH_REFS : PATH_DARK_REFS;
    }

    private static String getPublicFromRefContents(Repository repository, long prId) throws IOException {
        Path path = MoreFiles.resolve(repository.getPath(), PATH_REFS, PATH_PULL_REQUESTS, Long.toString(prId), "from");
        return MoreFiles.toString(path, UTF_8);
    }

    private PullRequestServiceGrpc.PullRequestServiceBlockingStub stub() {
        return PullRequestServiceGrpc.newBlockingStub(managedChannel);
    }
}