package com.atlassian.bitbucket.mesh.repository;

import com.atlassian.bitbucket.mesh.AbstractWiredTest;
import com.atlassian.bitbucket.mesh.execution.ExecutionManager;
import com.atlassian.bitbucket.mesh.git.GitTagType;
import com.atlassian.bitbucket.mesh.git.binary.GitBinary;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderFactory;
import com.atlassian.bitbucket.mesh.git.transcode.TranscodeService;
import com.atlassian.bitbucket.mesh.junit.LogListener;
import com.atlassian.bitbucket.mesh.test.annotation.DirtiesRepo;
import com.atlassian.bitbucket.mesh.test.annotation.Repo;
import org.junit.Rule;
import org.junit.Test;

import javax.annotation.Resource;
import java.nio.file.Path;
import java.nio.file.Paths;

import static com.atlassian.bitbucket.mesh.git.GitTagType.ANNOTATED;
import static com.atlassian.bitbucket.mesh.git.GitTagType.LIGHTWEIGHT;
import static com.atlassian.bitbucket.mesh.repository.RefHasher.NULL_HASH;
import static com.atlassian.bitbucket.mesh.test.annotation.Repo.Type.MANAGED;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class DefaultRepositoryHashServiceWiredTest extends AbstractWiredTest {

    @Rule
    public final LogListener logListener = new LogListener("com.zaxxer.nuprocess");

    @Resource
    private GitProcessBuilderFactory builderFactory;
    @Resource
    private GitBinary gitBinary;
    @Resource
    private ExecutionManager executionManager;
    @Repo(type = MANAGED)
    private Repository repository;
    @Resource
    private RepositoryHashService repositoryHashService;
    @Resource
    private RepositoryManager repositoryManager;
    @Resource
    private TranscodeService transcodeService;

    @DirtiesRepo
    @Test
    public void testBranchCreationThenDeletionNegatesHashChange() {
        String initialHash = repositoryHashService.getContentHash(repository);

        createBranch(repository, "test-branch", "master");
        deleteBranch(repository, "test-branch");
        assertEquals(initialHash, repositoryHashService.getContentHash(repository));
    }

    @DirtiesRepo
    @Test
    public void testBranchCreationUpdatesContentHash() {
        String initialHash = repositoryHashService.getContentHash(repository);

        createBranch(repository, "test-branch", "master");
        assertNotEquals(initialHash, repositoryHashService.getContentHash(repository));
    }

    @DirtiesRepo
    @Test
    public void testBranchDeletionThenCreationNegatesHashChange() {
        createBranch(repository, "test-branch", "master");

        String initialHash = repositoryHashService.getContentHash(repository);
        deleteBranch(repository, "test-branch");
        createBranch(repository, "test-branch", "master");
        assertEquals(initialHash, repositoryHashService.getContentHash(repository));
    }

    @DirtiesRepo
    @Test
    public void testBranchDeletionUpdatesContentHash() {
        String initialHash = repositoryHashService.getContentHash(repository);

        deleteBranch(repository, "branch_mod_merge");
        assertNotEquals(initialHash, repositoryHashService.getContentHash(repository));
    }

    @Test
    public void testGetContentHash() {
        assertEquals("af214f95e1876b5e2b86d502ab68b5d9abcad3b9e9f37c08603112232da12a86",
                repositoryHashService.getContentHash(repository));
    }

    @Test
    public void testGetContentHashDeletedRepository() {
        Repository repository = mock(Repository.class);
        Path repoDir = Paths.get("/does/not/exist/at/all.git");
        when(repository.getPath()).thenReturn(repoDir);
        when(repository.getHooksDir()).thenReturn(repoDir.resolve("hooks"));
        when(repository.getId()).thenReturn("repo-id");

        assertEquals(NULL_HASH, repositoryHashService.getContentHash(repository));
    }

    @Test
    public void testGetMetadataHash() {
        assertEquals("a00d43b206831512d56636f166a149d9f2164beadd50c30f0918cb876b83c2c4",
                repositoryHashService.getMetadataHash(repository));
    }

    @DirtiesRepo
    @Test
    public void testGetMetadataHashDefaultBranchChangesHash() {
        String initialHash = repositoryHashService.getMetadataHash(repository);
        repositoryManager.setDefaultBranch(repository, "basic_branching");
        assertNotEquals(initialHash, repositoryHashService.getMetadataHash(repository));
    }

    @DirtiesRepo
    @Test
    public void testGetMetadataHashTranscodeSettingChangesHash() {
        String initialHash = repositoryHashService.getMetadataHash(repository);
        transcodeService.setEnabled(repository, !transcodeService.isEnabled(repository));
        assertNotEquals(initialHash, repositoryHashService.getMetadataHash(repository));
    }

    @DirtiesRepo
    @Test
    public void testReplacingAnnotatedTagWithLightweightChangesContentHash() {
        createTag(repository, "test-tag", "master", ANNOTATED);
        String annotated = repositoryHashService.getContentHash(repository);
        deleteTag(repository, "test-tag");

        createTag(repository, "test-tag", "master", LIGHTWEIGHT);
        String lightweight = repositoryHashService.getContentHash(repository);

        assertNotEquals(lightweight, annotated);
    }

    @DirtiesRepo
    @Test
    public void testTagCreationUpdatesContentHash() {
        String initialHash = repositoryHashService.getContentHash(repository);

        createTag(repository, "test-lightweight-tag", "master", LIGHTWEIGHT);
        assertNotEquals(initialHash, repositoryHashService.getContentHash(repository));
    }

    @DirtiesRepo
    @Test
    public void testTagDeletionUpdatesContentHash() {
        String initialHash = repositoryHashService.getContentHash(repository);

        deleteTag(repository, "signed_tag");
        assertNotEquals(initialHash, repositoryHashService.getContentHash(repository));
    }

    private void createBranch(Repository repository, String name, String startPoint) {
        withNewExecutionContext(() -> builderFactory.builder(repository)
                .branch()
                .create(name)
                .startPoint(startPoint)
                .build()
                .run());
    }

    private void createTag(Repository repository, String name, String startPoint, GitTagType type) {
        withNewExecutionContext(() -> builderFactory.builder(repository)
                .tag()
                .create(name)
                .author("user", "user@email.com")
                .type(type)
                .startPoint(startPoint)
                .build()
                .run());
    }

    private void deleteBranch(Repository repository, String name) {
        withNewExecutionContext(() -> builderFactory.builder(repository)
                .branch()
                .delete(name)
                .build()
                .run());
    }

    private void deleteTag(Repository repository, String name) {
        withNewExecutionContext(() -> builderFactory.builder(repository)
                .tag()
                .delete(name)
                .build()
                .run());
    }

    private void withNewExecutionContext(Runnable runnable) {
        executionManager.withContext(context -> {
            runnable.run();
            return null;
        });
    }
}
