package com.atlassian.stash.internal.plugin;

import com.atlassian.bitbucket.util.FileUtils;
import com.atlassian.plugin.Plugin;
import com.atlassian.plugin.PluginRestartState;
import com.atlassian.plugin.PluginState;
import com.atlassian.plugin.manager.PluginPersistentState;
import com.atlassian.stash.internal.ApplicationSettings;
import com.atlassian.stash.internal.BuildInfo;
import org.junit.Before;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;

import static org.junit.Assert.*;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class PluginUpgradeHelperTest {

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    @Mock
    private BuildInfo buildInfo;
    @Mock
    private Plugin customPlugin;
    @Mock
    private PluginPersistentState pluginPersistentState;
    @Mock
    private ApplicationSettings settings;
    @Mock
    private ClusteredPluginPersistentStateStore store;

    @InjectMocks
    private PluginUpgradeHelper pluginUpgradeHelper;

    private File configDir;
    private File upgrades;

    @Before
    public void setUp() throws Exception {
        configDir = temporaryFolder.newFolder();
        upgrades = FileUtils.mkdir(configDir, "upgrades");

        when(settings.getConfigDir()).thenReturn(configDir);
        when(buildInfo.getBuildNumber()).thenReturn("400000");
        when(customPlugin.getKey()).thenReturn("custom.plugin");
        when(store.load()).thenReturn(pluginPersistentState);
    }

    @Test
    public void testWithUpdgradeShouldCreateAMarkerFileIfItDoesNotExist() {
        Runnable runnable = () -> assertNotSame(pluginPersistentState, pluginUpgradeHelper.wrap(pluginPersistentState));

        pluginUpgradeHelper.withUpgrade(runnable);

        assertTrue(Files.exists(upgrades.toPath().resolve("disable-plugins")));
    }

    @Test
    public void testWithUpdgradeShouldNotWrapPluginPersistentStateIfMarkerFileExists() throws IOException {
        Files.createFile(upgrades.toPath().resolve("disable-plugins"));
        Runnable runnable = () -> assertSame(pluginPersistentState, pluginUpgradeHelper.wrap(pluginPersistentState));

        pluginUpgradeHelper.withUpgrade(runnable);
    }

    @Test
    public void testWithUpdgradeShouldDisableACustomPluginIfItExists() {
        Runnable runnable = () -> assertFalse(pluginUpgradeHelper.wrap(pluginPersistentState).isEnabled(customPlugin));

        pluginUpgradeHelper.withUpgrade(runnable);

        assertTrue(pluginUpgradeHelper.isAnyPluginDisabled());

        ArgumentCaptor<PluginPersistentState> state = ArgumentCaptor.forClass(PluginPersistentState.class);
        verify(store).save(state.capture());
        assertFalse(state.getValue().isEnabled(customPlugin));
    }

    @Test
    public void testWithUpdgradeShouldWhenAMarkerGetsCreatedByAnotherNode() {
        Runnable runnable = () -> {
            assertFalse(pluginUpgradeHelper.wrap(pluginPersistentState).isEnabled(customPlugin));
            try {
                Files.createFile(upgrades.toPath().resolve("disable-plugins"));
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        };

        pluginUpgradeHelper.withUpgrade(runnable);

        assertTrue(pluginUpgradeHelper.isAnyPluginDisabled());

        verify(store).markReadOnly();
        ArgumentCaptor<PluginPersistentState> state = ArgumentCaptor.forClass(PluginPersistentState.class);
        verify(store).save(state.capture());
        assertFalse(state.getValue().isEnabled(customPlugin));
    }

    @Test
    public void testStreamGuardPluginShouldBeRemovedOnRestart() {
        Runnable runnable = () -> assertEquals(pluginUpgradeHelper.wrap(pluginPersistentState)
                .getPluginRestartState("com.atlassian.stash.plugin.stash-stream-guard-plugin"), PluginRestartState.REMOVE);

        pluginUpgradeHelper.withUpgrade(runnable);
    }

    @Test
    public void testRequiredPluginsMustBeEnabledIfCurrentlyDisabled() {
        String stpKey = "com.atlassian.support.stp";

        Plugin stp = Mockito.mock(Plugin.class);
        when(stp.isBundledPlugin()).thenReturn(true);
        when(stp.getPluginState()).thenReturn(PluginState.DISABLED);

        when(stp.getKey()).thenReturn(stpKey);
        when(pluginPersistentState.isEnabled(isA(Plugin.class))).thenAnswer(answer -> {
            Plugin plugin = (Plugin) answer.getArguments()[0];
            return !plugin.getKey().equalsIgnoreCase(stpKey) && plugin.getPluginState() == PluginState.ENABLED;
        });

        // STP should be disabled at the outset
        assertEquals(stp.getPluginState(), PluginState.DISABLED);

        // The Plugin upgrade helper should report that STP is enabled
        Runnable runnable = () -> assertTrue(pluginUpgradeHelper.wrap(pluginPersistentState).isEnabled(stp));

        pluginUpgradeHelper.withUpgrade(runnable);

        ArgumentCaptor<PluginPersistentState> state = ArgumentCaptor.forClass(PluginPersistentState.class);
        verify(store).save(state.capture());

        // Then STP should be enabled in the DB
        assertTrue(state.getValue().isEnabled(stp));
        // The disabled during upgrade banner should not be displayed when no plugins were disabled.
        assertFalse(pluginUpgradeHelper.isAnyPluginDisabled());
    }
}
