package com.atlassian.stash.internal.johnson;

import com.atlassian.johnson.JohnsonEventContainer;
import com.atlassian.johnson.event.Event;
import com.atlassian.johnson.event.EventLevel;
import com.atlassian.stash.internal.home.HomeDirectoryDetails;
import org.apache.commons.lang3.SystemUtils;
import org.hamcrest.Matchers;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import javax.servlet.ServletContext;
import java.io.File;

import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

@RunWith(MockitoJUnitRunner.class)
public class HomeApplicationEventCheckTest extends BaseJohnsonTest {

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();
    @Mock
    private ServletContext context;
    @Mock
    private JohnsonEventContainer container;
    @Captor
    private ArgumentCaptor<Event> eventCaptor;

    @Test
    public void testCanNotResolveDir() throws Exception {
        HomeApplicationEventCheck check = new HomeApplicationEventCheck() {
            @Override
            protected HomeDirectoryDetails resolveHomeDirectory() {
                throw new IllegalStateException("can-not-resolve-home-dir");
            }
        };

        check.check(container, context);
        verifyEvent(EventLevel.FATAL, "can-not-resolve-home-dir");
    }

    @Test
    public void testHomeIsAFile() throws Exception {
        File file = temporaryFolder.newFile();
        new Check(file, temporaryFolder.newFolder()).check(container, context);

        verifyEvent(EventLevel.FATAL, "is not a directory", file.getAbsolutePath());
    }

    @Test
    public void testSharedHomeIsAFile() throws Exception {
        File file = temporaryFolder.newFile();
        new Check(temporaryFolder.newFolder(), file).check(container, context);

        verifyEvent(EventLevel.FATAL, "is not a directory", file.getAbsolutePath());
    }

    @Test
    public void testHomeIsNotADirectory() throws Exception {
        File dir = temporaryFolder.newFolder();
        assertTrue(dir.delete());
        new Check(dir, temporaryFolder.newFolder()).check(container, context);

        verifyEvent(EventLevel.FATAL, "cannot be created", dir.getAbsolutePath());
    }

    @Test
    public void testSharedHomeIsNotADirectory() throws Exception {
        File dir = temporaryFolder.newFolder();
        assertTrue(dir.delete());
        new Check(temporaryFolder.newFolder(), dir).check(container, context);

        verifyNoMoreInteractions(container);
    }

    @Test
    public void testHomeIsNotReadable() throws Exception {
        Assume.assumeTrue(SystemUtils.IS_OS_UNIX);

        final File dir = temporaryFolder.newFolder();
        assertTrue("Could not change permissions on " + dir, dir.setReadable(false, false));
        new Check(dir, temporaryFolder.newFolder()).check(container, context);

        verifyEvent(EventLevel.FATAL, "exists but is not readable", dir.getAbsolutePath());
    }

    @Test
    public void testSharedHomeIsNotReadable() throws Exception {
        Assume.assumeTrue(SystemUtils.IS_OS_UNIX);

        final File dir = temporaryFolder.newFolder();
        assertTrue("Could not change permissions on " + dir, dir.setReadable(false, false));
        new Check(temporaryFolder.newFolder(), dir).check(container, context);

        verifyEvent(EventLevel.FATAL, "exists but is not readable", dir.getAbsolutePath());
    }

    @Test
    public void testHomeIsNotWritable() throws Exception {
        Assume.assumeTrue(SystemUtils.IS_OS_UNIX);

        final File dir = temporaryFolder.newFolder();
        assertTrue("Could not change permissions on " + dir, dir.setWritable(false, false));
        new Check(dir, temporaryFolder.newFolder()).check(container, context);

        verifyEvent(EventLevel.FATAL, "exists but is not writable", dir.getAbsolutePath());
    }

    @Test
    public void testSharedHomeIsNotWritable() throws Exception {
        Assume.assumeTrue(SystemUtils.IS_OS_UNIX);

        final File dir = temporaryFolder.newFolder();
        assertTrue("Could not change permissions on " + dir, dir.setWritable(false, false));
        new Check(temporaryFolder.newFolder(), dir).check(container, context);

        verifyEvent(EventLevel.FATAL, "exists but is not writable", dir.getAbsolutePath());
    }

    @Test
    public void testHomeIsNotExecutable() throws Exception {
        Assume.assumeTrue(SystemUtils.IS_OS_UNIX);

        final File dir = temporaryFolder.newFolder();
        assertTrue("Could not change permissions on " + dir, dir.setExecutable(false, false));
        new Check(dir, temporaryFolder.newFolder()).check(container, context);

        verifyEvent(EventLevel.FATAL, "exists but is not accessible", dir.getAbsolutePath());
    }

    @Test
    public void testSharedHomeIsNotExecutable() throws Exception {
        Assume.assumeTrue(SystemUtils.IS_OS_UNIX);

        final File dir = temporaryFolder.newFolder();
        assertTrue("Could not change permissions on " + dir, dir.setExecutable(false, false));
        new Check(temporaryFolder.newFolder(), dir).check(container, context);

        verifyEvent(EventLevel.FATAL, "exists but is not accessible", dir.getAbsolutePath());
    }

    private void verifyEvent(String level, String fragment) {
        Event event = getGeneratedEvent();
        assertEquals(level, event.getLevel().getLevel());
        assertThat(event.getDesc(), containsString(fragment));
    }

    private void verifyEvent(String level, String fragment, String path) {
        Event event = getGeneratedEvent();
        assertEquals(level, event.getLevel().getLevel());
        assertThat(event.getDesc(), Matchers.<String>both(containsString(fragment)).and(containsString(path)));
    }

    private Event getGeneratedEvent() {
        verify(container).addEvent(eventCaptor.capture());
        return eventCaptor.getValue();
    }

    private class Check extends HomeApplicationEventCheck {
        private final File homeDir;
        private final File sharedHomeDir;

        private Check(File homeDir, File sharedHomeDir) {
            this.homeDir = homeDir;
            this.sharedHomeDir = sharedHomeDir;
        }

        @Override
        protected HomeDirectoryDetails resolveHomeDirectory() {
            return new HomeDirectoryDetails(homeDir, sharedHomeDir);
        }
    }
}
