package com.atlassian.bitbucket.mesh.app;

import com.atlassian.bitbucket.mesh.ApplicationDirs;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.junit.TemporaryDirectory;
import com.google.common.collect.ImmutableList;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.env.PropertyResolver;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

import static com.atlassian.bitbucket.mesh.env.SystemProperties.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.Silent.class)
public class ApplicationDirResolverTest {

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

    @Mock
    private PropertyResolver propertyResolver;
    @Mock //Don't use System.setProperty in the tests; it causes unexpected behavior in subsequent tests
    private PropertySetter propertySetter;
    @InjectMocks
    private ApplicationDirResolver resolver;

    @Test
    public void testResolveDirCreatesAbsolutePaths() {
        when(propertyResolver.getProperty(PROP_HOME_DIR)).thenReturn("mesh/home");

        Path actualHome = resolver.resolveHomeDir();
        Path inputHome = Paths.get("mesh/home");
        assertNotEquals("Resolved home path should be absolute", inputHome, actualHome);
        assertEquals(inputHome.toAbsolutePath(), actualHome);
    }

    @Test
    public void testResolveDirsCommaSeparated() {
        Path absolutePath = Paths.get("/var/mesh/data2").toAbsolutePath();
        when(propertyResolver.getProperty(PROP_DATA_DIRS))
                .thenReturn("mesh/data1, " + absolutePath);

        assertEquals(ImmutableList.of(Paths.get("mesh/data1").toAbsolutePath(), absolutePath),
                resolver.resolveDirs("data", PROP_DATA_DIRS, null, false));
    }

    @Test
    public void testResolveDirsPropertyListStyle() {
        Path absolutePath = Paths.get("/var/mesh/data2").toAbsolutePath();
        when(propertyResolver.getProperty(PROP_DATA_DIRS + "[0]")).thenReturn("mesh/data1");
        when(propertyResolver.getProperty(PROP_DATA_DIRS + "[1]")).thenReturn(absolutePath.toString());

        assertEquals(ImmutableList.of(Paths.get("mesh/data1").toAbsolutePath(), absolutePath),
                resolver.resolveDirs("data", PROP_DATA_DIRS, null, false));
    }

    @Test
    public void testResolveDirWithDefault() {
        Path homeDir = resolver.resolveDir("shared home", "bitbucket.shared-home", "bitbucket/shared");

        assertEquals(Paths.get("bitbucket/shared").toAbsolutePath(), homeDir);
    }

    @Test
    public void testResolveDirsWithDefault() {
        List<Path> dataDirs = resolver.resolveDirs("data", PROP_DATA_DIRS, "mesh/data", false);

        assertEquals(ImmutableList.of(Paths.get("mesh/data").toAbsolutePath()), dataDirs);
    }

    @Test
    public void testResolveDataDirsAddDefaultEvenWhenNotConfigured() throws IOException {
        Path home = tempDir.newFolder("home");
        Path store = tempDir.newFolder("store");

        when(propertyResolver.getProperty(PROP_HOME_DIR)).thenReturn(home.toString());
        when(propertyResolver.getProperty(PROP_DATA_DIRS)).thenReturn(store.toString());

        //If the default store directory does not exist, it should not be included in the list of data dirs
        List<Path> dataDirs = resolver.resolveDataDirs();
        assertEquals(ImmutableList.of(store), dataDirs);

        //But when the default store directory DOES exist, it should be included
        Path defaultStore = MoreFiles.mkdir(home.resolve(ApplicationDirs.DEFAULT_STORE));
        dataDirs = resolver.resolveDataDirs();
        assertEquals(ImmutableList.of(defaultStore, store), dataDirs);
    }

    @Test(expected = InvalidApplicationDirectoryException.class)
    public void testResolveDataDirsWithPropertyContainingTilde() {
        when(propertyResolver.getProperty(PROP_DATA_DIRS)).thenReturn("~/prop/data");

        resolver.resolveDataDirs();
    }

    @Test(expected = InvalidApplicationDirectoryException.class)
    public void testResolveWithHomePropertyContainingTilde() {
        when(propertyResolver.getProperty(PROP_HOME_DIR)).thenReturn("~/prop/home");

        resolver.resolveHomeDir();
    }

    @Test(expected = MissingApplicationDirectoryException.class)
    public void testResolveWithoutPropertySet() {
        resolver.resolveDir("cache", "mesh.cache", null);
    }

    @Test(expected = MissingApplicationDirectoryException.class)
    public void testResolveHomeWithoutPropertySet() {
        resolver.resolveHomeDir();
    }

    @Test(expected = InvalidApplicationDirectoryException.class)
    public void testResolveWhenDataPropertyEqualToInstallDir() {
        when(propertyResolver.getProperty(PROP_INSTALL_DIR)).thenReturn("prop/install");
        when(propertyResolver.getProperty(PROP_DATA_DIRS)).thenReturn("prop/install");

        resolver.resolveDataDirs();
    }

    @Test(expected = InvalidApplicationDirectoryException.class)
    public void testResolveWhenDataSystemPropertyIsSubdirectoryOfInstallDir() {
        when(propertyResolver.getProperty(PROP_INSTALL_DIR)).thenReturn("prop/install");
        when(propertyResolver.getProperty(PROP_DATA_DIRS)).thenReturn("prop/install/data");

        resolver.resolveDataDirs();
    }

    @Test(expected = InvalidApplicationDirectoryException.class)
    public void testResolveWhenHomePropertyEqualToInstallDir() {
        when(propertyResolver.getProperty(PROP_INSTALL_DIR)).thenReturn("prop/home");
        when(propertyResolver.getProperty(PROP_HOME_DIR)).thenReturn("prop/home");

        resolver.resolveHomeDir();
    }

    @Test(expected = InvalidApplicationDirectoryException.class)
    public void testResolveWhenHomeSystemPropertyIsSubdirectoryOfInstallDir() {
        when(propertyResolver.getProperty(PROP_INSTALL_DIR)).thenReturn("prop/home");
        when(propertyResolver.getProperty(PROP_HOME_DIR)).thenReturn("prop/home/data");

        resolver.resolveHomeDir();
    }

    @Test(expected = InvalidApplicationDirectoryException.class)
    public void testResolveWhenHomeSystemPropertyIsDeepSubdirectoryOfInstallDir() {
        when(propertyResolver.getProperty(PROP_INSTALL_DIR)).thenReturn("prop/home");
        when(propertyResolver.getProperty(PROP_HOME_DIR)).thenReturn("prop/home/data/deep/dir");

        resolver.resolveHomeDir();
    }

    @Test
    public void testResolveWhenHomePropertySiblingOfInstallDir() {
        when(propertyResolver.getProperty(PROP_INSTALL_DIR)).thenReturn("prop/home");
        when(propertyResolver.getProperty(PROP_HOME_DIR)).thenReturn("prop/home-data");

        assertEquals(Paths.get("prop/home-data").toAbsolutePath(), resolver.resolveHomeDir());
    }
}
