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.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;

import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

public class DefaultManagedRepositoryTest {

    private static final String HIERARCHY_ID = "abcdefghijklmnopqrst";
    private static final String PARTITION = "1001";

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

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

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

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

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

        //Attach storage to set the repository directory
        Path hierarchyDir = temporaryDirectory.getRoot();
        Path repoDir = temporaryDirectory.getRoot().resolve("1");
        ManagedShard shard = mock(ManagedShard.class);
        when(shard.getRepositoriesDir(PARTITION, 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 testFormatIdForIntPartition() {
        assertEquals("p/0001/h/" + HIERARCHY_ID + "/r/1", ManagedRepository.formatId(1, HIERARCHY_ID, "1"));
    }

    @Test
    public void testFormatIdForSharedHome() {
        assertEquals("p/0002/h/" + HIERARCHY_ID + "/r/1", ManagedRepository.formatId("0002", HIERARCHY_ID, "1"));
    }

    @Test
    public void testGetDirtyMarker() {
        DefaultManagedRepository repository = new DefaultManagedRepository(PARTITION, HIERARCHY_ID, "1");

        //Should throw when not attached
        assertThrows(IllegalStateException.class, repository::getDirtyMarker);

        attach(repository);
        Path expected = repository.getPath().getParent().resolve("shared-objects/app-info/dirty/1");
        assertEquals(expected, repository.getDirtyMarker());
    }

    @Test
    public void testGetPathWithoutStorageAttached() {
        ManagedRepository repository = new DefaultManagedRepository(PARTITION, HIERARCHY_ID, "1");

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

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

    @Test
    public void testGetVersion() {
        DefaultManagedRepository repository = new DefaultManagedRepository(PARTITION, HIERARCHY_ID, "1");

        //Attributes not yet attached
        assertThrows(IllegalStateException.class, repository::getVersion);

        //Attach attributes
        RepositoryAttributeManager attributeManager = mock(RepositoryAttributeManager.class);
        when(attributeManager.getVersion(repository.getId())).thenReturn(77L);
        repository.attachAttributes(attributeManager);

        assertEquals(77L, repository.getVersion());
    }

    @Test
    public void testIsDirty() throws IOException {
        DefaultManagedRepository repository = new DefaultManagedRepository(PARTITION, HIERARCHY_ID, "1");

        //Should throw when not attached
        assertThrows(IllegalStateException.class, repository::isDirty);

        //Attach and validate isDirty
        attach(repository);
        assertFalse(repository.isDirty());

        MoreFiles.mkdir(repository.getDirtyMarker().getParent());
        MoreFiles.touch(repository.getDirtyMarker());
        assertTrue(repository.isDirty());

        Files.delete(repository.getDirtyMarker());
        assertFalse(repository.isDirty());
    }

    @Test
    public void testParseId() {
        DefaultManagedRepository repository = DefaultManagedRepository.parseId("p/" + PARTITION + "/h/" + HIERARCHY_ID + "/r/1");
        assertNotNull(repository);
        assertEquals(PARTITION, repository.getPartition());
        assertEquals(HIERARCHY_ID, repository.getHierarchyId());
        assertEquals("1", repository.getLocalId());
    }

    @Test
    public void testParseIdForMultipleParams() {
        DefaultManagedRepository repository = DefaultManagedRepository.parseId(
                "p/" + PARTITION + "/h/" + HIERARCHY_ID + "/r/1?" + Repository.PARAM_QUARANTINE + "=0001&r=2");
        assertNotNull(repository);
        assertEquals(PARTITION, repository.getPartition());
        assertEquals(HIERARCHY_ID, repository.getHierarchyId());
        assertEquals("1", repository.getLocalId());
        assertEquals("0001", repository.getParameter(Repository.PARAM_QUARANTINE));
        assertEquals("2", repository.getParameter("r"));
    }

    @Test
    public void testParseIdForQuarantineEnvironment() {
        DefaultManagedRepository repository = DefaultManagedRepository.parseId(
                "p/" + PARTITION + "/h/" + HIERARCHY_ID + "/r/1?" + Repository.PARAM_QUARANTINE + "=0001");
        assertNotNull(repository);
        assertEquals(PARTITION, repository.getPartition());
        assertEquals(HIERARCHY_ID, repository.getHierarchyId());
        assertEquals("1", repository.getLocalId());
        assertEquals("0001", repository.getParameter(Repository.PARAM_QUARANTINE));
    }

    @Test
    public void testParseIdForUUID() {
        String hierarchyId = UUID.randomUUID().toString();
        String localId = UUID.randomUUID().toString();
        DefaultManagedRepository repository = DefaultManagedRepository.parseId(
                "p/" + PARTITION + "/h/" + hierarchyId + "/r/" + localId);
        assertNotNull(repository);
        assertEquals(PARTITION, repository.getPartition());
        assertEquals(hierarchyId, repository.getHierarchyId());
        assertEquals(localId, repository.getLocalId());
    }

    @Test
    public void testParseIdForWorkTree() {
        assertNull(DefaultManagedRepository.parseId("p/" + PARTITION + "/h/" + HIERARCHY_ID + "/r/1/wt/0001"));
    }

    @Test
    public void testParseIdWithBitbucketServerId() {
        assertNull(DefaultManagedRepository.parseId(BitbucketServerRepository.formatId("1", HIERARCHY_ID, "1")));
    }

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

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

        ManagedShard shard = mock(ManagedShard.class);
        when(shard.getRepositoriesDir(eq(PARTITION), eq(HIERARCHY_ID))).thenReturn(repositoriesDir);

        DefaultManagedRepository repository = new DefaultManagedRepository(PARTITION, HIERARCHY_ID, "1");
        repository.attachTo(shard);

        //Verify the expected repositoryDir was set
        assertEquals(repositoriesDir.resolve("1"), repository.getPath());
        assertEquals(repositoriesDir.resolve("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(PARTITION), eq(HIERARCHY_ID));

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

    private void attach(DefaultManagedRepository repository) {
        Path rootDir = Paths.get("root");
        Path repositoriesDir = rootDir.resolve("repositories");

        ManagedShard shard = mock(ManagedShard.class);
        when(shard.getRepositoriesDir(eq(repository.getPartition()), eq(repository.getHierarchyId())))
                .thenReturn(repositoriesDir);
        DefaultSharedObjectsRepository sharedObjects =
                new DefaultSharedObjectsRepository(shard, repository.getPartition(), repository.getHierarchyId());
        when(shard.getSharedObjects(repository.getPartition(), repository.getHierarchyId())).thenReturn(sharedObjects);

        repository.attachTo(shard);
    }
}