package com.atlassian.bitbucket.installer.scripts;

import com.atlassian.bitbucket.installer.InstallationDetails;
import com.atlassian.bitbucket.installer.InstallationException;
import com.atlassian.bitbucket.server.ApplicationMode;
import com.install4j.api.context.InstallerContext;
import org.apache.commons.lang3.SystemUtils;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.util.EnumSet;
import java.util.Set;

import static java.nio.file.attribute.PosixFilePermission.*;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

public class BitbucketConfigTest {
    @Rule
    public final MockitoRule initMockito = MockitoJUnit.rule();
    @Rule
    public final TemporaryFolder folder = new TemporaryFolder();
    @Mock
    private InstallerContext installerContext;
    @Mock
    private InstallationDetails installationDetails;

    private Path configFile;
    private BitbucketConfig config;

    @Before
    public void setup() throws IOException {
        Path sharedHome = folder.getRoot().toPath().resolve("shared");
        Files.createDirectories(sharedHome);
        configFile = sharedHome.resolve(BitbucketConfig.FILE_NAME);
        config = new BitbucketConfig(sharedHome);
    }

    @Test
    public void testWriteMirrorModePersistsCorrectly() {
        config.setApplicationMode(ApplicationMode.MIRROR).write();

        assertThat(configFile, isConfigFile().inMirrorMode());
    }

    @Test
    public void testWriteDefaultModeIsNoOpWhenFileIsAlreadyEmpty() {
        config.setApplicationMode(ApplicationMode.DEFAULT).write();

        assertThat(configFile, isConfigFile().inDefaultMode());
        assertThat(configFile, not(FilesMatcher.exists()));
    }

    @Test
    public void testWriteDefaultFailsWhenFileExists() {

        configGenerator().mirrorMode().write();

        try {
            config.setApplicationMode(ApplicationMode.DEFAULT).write();
        } catch (InstallationException e) {
            assertThat(e.getMessageKey(), equalTo(InstallationException.ERROR_UNABLE_TO_WRITE_CONFIG));
        }

        assertThat(configFile, isConfigFile().inMirrorMode());
    }

    @Test
    public void testWriteFailsToOverwriteExistingProperties() {

        String propertyKey = "random";
        String propertyValue = "property";

        configGenerator().put(propertyKey, propertyValue).write();

        try {
            config.setApplicationMode(ApplicationMode.MIRROR).write();
        } catch (InstallationException e) {
            assertThat(e.getMessageKey(), equalTo(InstallationException.ERROR_UNABLE_TO_WRITE_CONFIG));
        }

        assertThat(configFile, isConfigFile().withProperty(propertyKey, propertyValue));
    }

    @Test
    public void testWriteFails() throws IOException {
        configGenerator().mirrorMode().write();
        readOnly(configFile);

        try {
            config.setApplicationMode(ApplicationMode.MIRROR).write();
            fail("Expected exception");
        } catch (InstallationException expected) {
            assertThat(expected.getMessageKey(), equalTo(InstallationException.ERROR_UNABLE_TO_WRITE_CONFIG));
        }
    }

    private static void readOnly(Path path) {

        Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS);

        try {
            PosixFileAttributeView fileAttributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class);
            Set<PosixFilePermission> posixFileAttributes = EnumSet.copyOf(fileAttributeView.readAttributes().permissions());
            posixFileAttributes.removeAll(EnumSet.of(GROUP_WRITE, OTHERS_WRITE, OWNER_WRITE));
            fileAttributeView.setPermissions(posixFileAttributes);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private ConfigFileGenerator configGenerator() {
        return new ConfigFileGenerator(configFile);
    }

    private static BitbucketConfigMatcher isConfigFile() {
        return new BitbucketConfigMatcher();
    }

    private static class FilesMatcher {
        public static Matcher<Path> exists() {
            return new TypeSafeDiagnosingMatcher<Path>() {
                @Override
                protected boolean matchesSafely(Path item, Description mismatchDescription) {
                    if (Files.exists(item)) {
                        return true;
                    } else {
                        mismatchDescription.appendValue(item).appendValue(" does not exist");
                        return false;
                    }
                }

                @Override
                public void describeTo(Description description) {
                    description.appendValue("Path exists");
                }
            };
        }
    }
}