package com.atlassian.stash.internal.scm.git.command.tag;

import com.atlassian.bitbucket.NoSuchObjectException;
import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.scm.annotation.DirtiesRepo;
import com.atlassian.bitbucket.scm.annotation.Repo;
import com.atlassian.bitbucket.scm.git.GitAgent;
import com.atlassian.bitbucket.scm.git.GitRefPattern;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.GitCommandFactory;
import com.atlassian.bitbucket.scm.git.command.tag.GitTag;
import com.atlassian.bitbucket.scm.git.command.tag.GitTagType;
import com.atlassian.bitbucket.user.Person;
import com.atlassian.bitbucket.user.SimplePerson;
import com.atlassian.stash.internal.scm.git.AbstractGitTest;
import org.apache.commons.lang3.SystemUtils;
import org.junit.Assume;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

@SuppressWarnings("ConstantConditions")
public class TagCommandTest extends AbstractGitTest {

    private static final Person AUTHOR = new SimplePerson("Git Tag Command Test", "git@tag.test");

    @Autowired
    private GitAgent agent;
    @Autowired
    private GitCommandBuilderFactory builderFactory;
    @Autowired
    private GitCommandFactory commandFactory;
    @Repo
    private Repository repository;

    @DirtiesRepo
    @Test
    public void testCreateTag() throws Exception {
        Branch originalHead = agent.resolveHead(repository);
        String tagName = "new-tag" + System.currentTimeMillis();

        tagBuilder().create(tagName)
                .author(AUTHOR)
                .message("A new ANNOTATED tag").build().call();

        Ref ref = commandFactory.resolveRef(repository, tagName).call();
        assertTrue(ref instanceof Tag);
        assertEquals(tagName, ref.getDisplayId());
        assertEquals(GitRefPattern.TAGS.qualify(tagName), ref.getId());
        assertEquals(originalHead.getLatestCommit(), ref.getLatestCommit());
        assertNotNull(((Tag) ref).getHash());
    }

    @DirtiesRepo
    @Test
    public void testCreateLightweightTag() throws Exception {
        Branch originalHead = agent.resolveHead(repository);
        String tagName = "new-tag" + System.currentTimeMillis();

        tagBuilder().create(tagName)
                .author(AUTHOR)
                .type(GitTagType.LIGHTWEIGHT).build().call();

        Ref ref = commandFactory.resolveRef(repository, tagName).call();
        assertTrue(ref instanceof Tag);
        assertEquals(tagName, ref.getDisplayId());
        assertEquals(GitRefPattern.TAGS.qualify(tagName), ref.getId());
        assertEquals(originalHead.getLatestCommit(), ref.getLatestCommit());
        assertNull(((Tag) ref).getHash());
    }

    @DirtiesRepo
    @Test
    public void testCreateTagWithStartPoint() throws Exception {
        Branch branch = agent.resolveBranch(repository, "basic_branching");
        String tagName = "new-tag" + System.currentTimeMillis();

        tagBuilder().create(tagName)
                .author(AUTHOR)
                .message("A new ANNOTATED tag")
                .startPoint("basic_branching").build().call();

        Ref ref = commandFactory.resolveRef(repository, tagName).call();
        assertTrue(ref instanceof Tag);
        assertEquals(tagName, ref.getDisplayId());
        assertEquals(GitRefPattern.TAGS.qualify(tagName), ref.getId());
        assertEquals(branch.getLatestCommit(), ref.getLatestCommit());
        assertNotNull(((Tag) ref).getHash());
    }

    @DirtiesRepo
    @Test
    public void testCreateTagWithForceShouldUpdateExistingTag() throws Exception {
        Branch branch = agent.resolveBranch(repository, "basic_branching");
        String tagName = "new-tag" + System.currentTimeMillis();

        tagBuilder().create(tagName)
                .author(AUTHOR).build().call();

        tagBuilder().create(tagName)
                .author(AUTHOR)
                .force(true)
                .message("A tag pointing to basic_branching")
                .startPoint("basic_branching").build().call();

        Ref ref = commandFactory.resolveRef(repository, tagName).call();
        assertTrue(ref instanceof Tag);
        assertEquals(tagName, ref.getDisplayId());
        assertEquals(GitRefPattern.TAGS.qualify(tagName), ref.getId());
        assertEquals(branch.getLatestCommit(), ref.getLatestCommit());
        assertNotNull(((Tag) ref).getHash());
    }

    @DirtiesRepo
    @Test(expected = DuplicateRefException.class)
    public void testCreateTagWithExistingTagNameShouldFail() throws Exception {
        String tagName = "new-tag" + System.currentTimeMillis();

        tagBuilder().create(tagName)
                .author(AUTHOR).build().call();

        tagBuilder().create(tagName)
                .author(AUTHOR)
                .message("Second try with the same tag name").build().call();
    }

    @DirtiesRepo()
    @Test(expected = ConflictingRefNameException.class)
    public void testCreateConflictingTagFileDirLoose() throws Exception {
        tagBuilder().create("release/v1.0")
                .author(AUTHOR)
                .build().call();
        tagBuilder().create("release")
                .author(AUTHOR)
                .build().call();
    }

    @DirtiesRepo()
    @Test(expected = ConflictingRefNameException.class)
    public void testCreateConflictingTagDirFileLoose() throws Exception {
        // Git on Windows produces an ambiguous error message when loose refs are involved, so we don't throw
        // a ConflictingRefNameException, but instead the more general CommandFailedException
        Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS);

        tagBuilder().create("release")
                .author(AUTHOR)
                .build().call();
        tagBuilder().create("release/v1.0")
                .author(AUTHOR)
                .build().call();
    }

    @DirtiesRepo()
    @Test(expected = ConflictingRefNameException.class)
    public void testCreateConflictingTagFileDirPacked() throws Exception {
        tagBuilder().create("release/v1.0")
                .author(AUTHOR)
                .build().call();
        runGarbageCollection();
        tagBuilder().create("release")
                .author(AUTHOR)
                .build().call();
    }

    @DirtiesRepo()
    @Test(expected = ConflictingRefNameException.class)
    public void testCreateConflictingTagDirFilePacked() throws Exception {
        tagBuilder().create("release")
                .author(AUTHOR)
                .build().call();
        runGarbageCollection();
        tagBuilder().create("release/v1.0")
                .author(AUTHOR)
                .build().call();
    }

    @Test(expected = InvalidRefNameException.class)
    public void testCreateTagWithInvalidTagNameShouldFail() throws Exception {
        tagBuilder().create("&^%$&*^invalid_tag_name")
                .author(AUTHOR).build().call();
    }

    @Test(expected = NoSuchObjectException.class)
    public void testCreateTagWithInvalidStartPointShouldFail() throws Exception {
        tagBuilder().create("this_tag_should_not_be_created")
                .author(AUTHOR)
                .startPoint("*&%^invalid-start-point^%").build().call();
    }

    @DirtiesRepo
    @Test
    public void testDeleteTag() throws Exception {
        String tagName = "backdated_annotated_tag";

        tagBuilder().delete(tagName)
                .author(AUTHOR).build().call();

        assertNull(commandFactory.resolveRef(repository, tagName).call());
    }

    @Test(expected = NoSuchTagException.class)
    public void testDeleteTagShouldFailIfTagDoesNotExist() throws Exception {
        tagBuilder().delete("invalid_tag_name")
                .author(AUTHOR).build().call();
    }

    private void runGarbageCollection() {
        builderFactory.builder(repository).gc().build().call();
    }

    private GitTag tagBuilder() {
        return builderFactory.builder(repository).tag();
    }
}
