package com.atlassian.bitbucket.mesh.repository;

import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.junit.TemporaryDirectory;
import org.junit.Rule;
import org.junit.Test;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import static com.atlassian.bitbucket.mesh.repository.SharedHomeBitbucketServerShard.ID;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

public class DefaultBitbucketServerRepositoryTest {

    private static final String HIERARCHY_ID = "abcdefghijklmnopqrst";

    @Rule
    public final TemporaryDirectory temporaryDirectory = new TemporaryDirectory();

    @Test
    public void testAccept() {
        Boolean result = new DefaultBitbucketServerRepository(ID, HIERARCHY_ID, "1").accept(new RepositoryVisitor<Boolean>() {

            @Override
            public Boolean defaultValue(Repository repository) {
                return Boolean.FALSE;
            }

            @Override
            public Boolean visit(@Nonnull BitbucketServerRepository repository) {
                return Boolean.TRUE;
            }
        });
        assertEquals("accept invoked the wrong visitor method", Boolean.TRUE, result);
    }

    @Test
    public void testExists() throws IOException {
        DefaultBitbucketServerRepository repository = new DefaultBitbucketServerRepository(ID, HIERARCHY_ID, "1");

        //Attach storage to set the repository directory
        Path hierarchyDir = temporaryDirectory.getRoot();
        Path repoDir = hierarchyDir.resolve("1");
        BitbucketServerShard shard = mock(BitbucketServerShard.class);
        when(shard.getRepositoriesDir(HIERARCHY_ID)).thenReturn(hierarchyDir);
        repository.attachTo(shard);

        //Repository directory does not exist (yet)
        assertFalse(repository.exists());

        //Create the repository directory
        MoreFiles.mkdir(repoDir);
        assertTrue(repository.exists());

        //Mark the repository as deleted
        MoreFiles.touch(repository.getDeletedMarker());
        assertFalse(repository.exists());

        //Delete the repository directory
        MoreFiles.deleteRecursively(repoDir);
        assertFalse(repository.exists());
    }

    @Test
    public void testFormatIdForLegacyDataStore() {
        assertEquals("ds/1/h/" + HIERARCHY_ID + "/r/1",
                BitbucketServerRepository.formatId("1", HIERARCHY_ID, "1"));
    }

    @Test
    public void testFormatIdForSharedHome() {
        assertEquals("ds/" + ID + "/h/" + HIERARCHY_ID + "/r/1",
                BitbucketServerRepository.formatId(HIERARCHY_ID, "1"));
    }

    @Test
    public void testGetPathWithoutStorageAttached() {
        DefaultBitbucketServerRepository repository = new DefaultBitbucketServerRepository(ID, HIERARCHY_ID, "1");

        IllegalStateException e = assertThrows(IllegalStateException.class, repository::getPath);
        assertEquals("Storage is not attached", e.getMessage());
    }

    @Test
    public void testGetSlug() {
        DefaultBitbucketServerRepository repository = new DefaultBitbucketServerRepository(ID, HIERARCHY_ID, "1");
        assertEquals(HIERARCHY_ID + "-1", repository.getSlug());
        assertEquals(repository.getSlug(), repository.toString());
    }

    @Test
    public void testParseIdForLegacyDataStore() {
        DefaultBitbucketServerRepository repository = DefaultBitbucketServerRepository.parseId("ds/1/h/" + HIERARCHY_ID + "/r/1");
        assertNotNull(repository);
        assertEquals("1", repository.getShardId());
        assertEquals(HIERARCHY_ID, repository.getHierarchyId());
        assertEquals("1", repository.getBitbucketId());
    }

    @Test
    public void testParseIdForMultipleParams() {
        DefaultBitbucketServerRepository repository = DefaultBitbucketServerRepository.parseId("ds/2/h/" +
                HIERARCHY_ID + "/r/1?" + Repository.PARAM_QUARANTINE + "=0001&r=2");
        assertNotNull(repository);
        assertEquals("2", repository.getShardId());
        assertEquals(HIERARCHY_ID, repository.getHierarchyId());
        assertEquals("1", repository.getBitbucketId());
        assertEquals("0001", repository.getParameter(Repository.PARAM_QUARANTINE));
        assertEquals("2", repository.getParameter("r"));
    }

    @Test
    public void testParseIdForQuarantineEnvironment() {
        DefaultBitbucketServerRepository repository = DefaultBitbucketServerRepository.parseId("ds/2/h/" +
                HIERARCHY_ID + "/r/1?" + Repository.PARAM_QUARANTINE + "=0001");
        assertNotNull(repository);
        assertEquals("2", repository.getShardId());
        assertEquals(HIERARCHY_ID, repository.getHierarchyId());
        assertEquals("1", repository.getBitbucketId());
        assertEquals("0001", repository.getParameter(Repository.PARAM_QUARANTINE));
    }

    @Test
    public void testParseIdForSharedHome() {
        DefaultBitbucketServerRepository repository = DefaultBitbucketServerRepository.parseId("ds/" +
                ID + "/h/" + HIERARCHY_ID + "/r/1");
        assertNotNull(repository);
        assertEquals("0", repository.getShardId());
        assertEquals(HIERARCHY_ID, repository.getHierarchyId());
        assertEquals("1", repository.getBitbucketId());
    }

    @Test
    public void testParseIdForWorkTree() {
        assertNull(DefaultBitbucketServerRepository.parseId("ds/2/h/" + HIERARCHY_ID + "/r/1/wt/0001"));
    }

    @Test
    public void testParseIdWithManagedId() {
        assertNull(DefaultBitbucketServerRepository.parseId(ManagedRepository.formatId(0, HIERARCHY_ID, "1")));
    }

    @Test
    public void testParseIdWithPathId() {
        assertNull(DefaultBitbucketServerRepository.parseId(PathRepository.formatId("repositories/1")));
    }

    @Test
    public void testStorage() {
        Path rootDir = Paths.get("root");
        Path repositoriesDir = rootDir.resolve("repositories");

        BitbucketServerShard shard = mock(BitbucketServerShard.class);
        when(shard.getRepositoriesDir(eq(HIERARCHY_ID))).thenReturn(repositoriesDir);

        DefaultBitbucketServerRepository repository = new DefaultBitbucketServerRepository(ID, HIERARCHY_ID, "1");
        repository.attachTo(shard);

        //Verify the expected repositoryDir was set
        assertEquals(repositoriesDir.resolve("1"), repository.getPath());
        assertEquals(MoreFiles.resolve(repositoriesDir, "1", "DELETED"), repository.getDeletedMarker());

        //The following call/verify pairs verify the delegation for other storage methods to the shard
        repository.getHooksDir();
        verify(shard).getHooksDir();

        repository.getLfsDir();
        verify(shard).getLfsDir(eq(HIERARCHY_ID));

        repository.getTemplateDir();
        verify(shard).getTemplateDir();
    }
}