package com.atlassian.stash.internal.maintenance.backup;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.ApplicationSettings;
import com.atlassian.stash.internal.backup.BackupException;
import com.atlassian.stash.internal.db.DatabaseHandle;
import com.atlassian.stash.internal.jdbc.DataSourceConfiguration;
import com.google.common.io.Closeables;
import de.schlichtherle.truezip.zip.ZipEntry;
import de.schlichtherle.truezip.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
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.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.zip.ZipInputStream;

import static com.atlassian.stash.internal.maintenance.MaintenanceTaskTestHelper.assertProgress;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class ConfigurationBackupStepTest {
    private static final String CONTENT = "# example config content";
    private static final String DRIVER_CLASS_NAME = "driver.class";
    private static final String PASSWORD = "password";
    private static final String URL = "url";
    private static final String USER = "user";

    @Rule
    public TemporaryFolder tmpDir = new TemporaryFolder();
    @Spy
    private I18nService i18nService = new SimpleI18nService(SimpleI18nService.Mode.RETURN_KEYS);
    @Mock
    private DataSourceConfiguration configuration;
    @Mock
    private BackupState state;
    private ConfigurationBackupStep step;
    private ByteArrayOutputStream output;
    private ZipOutputStream zipStream;

    @Before
    public void setUp() {
        output = new ByteArrayOutputStream();
        zipStream = spy(new ZipOutputStream(output));

        DatabaseHandle handle = mock(DatabaseHandle.class);

        when(state.getBackupZipStream()).thenReturn(zipStream);
        when(state.getSourceDatabase()).thenReturn(handle);
        when(handle.getConfiguration()).thenReturn(configuration);

        when(configuration.getDriverClassName()).thenReturn(DRIVER_CLASS_NAME);
        when(configuration.getPassword()).thenReturn(PASSWORD);
        when(configuration.getUrl()).thenReturn(URL);
        when(configuration.getUser()).thenReturn(USER);

        ApplicationSettings applicationSettings = mock(ApplicationSettings.class);
        when(applicationSettings.getSharedHomeDir()).thenReturn(tmpDir.getRoot());

        step = new ConfigurationBackupStep(state, applicationSettings, i18nService);
    }

    @Test(expected = BackupException.class)
    public void testRunIoException() throws IOException {
        doThrow(new IOException("")).when(zipStream).putNextEntry(any(ZipEntry.class), anyBoolean());

        step.run();
    }

    @Test
    public void testRunNoConfigFilePresent() throws Exception {
        assertProgress("bitbucket.backup.configuration", 0, step.getProgress());

        step.run();

        String content = getContent();

        assertTrue(content.startsWith(ConfigurationBackupStep.BANNER + "\n"));
        assertTrue(content.contains("jdbc.driver=" + DRIVER_CLASS_NAME + "\n"));
        assertTrue(content.contains("jdbc.url=" + URL + "\n"));
        assertTrue(content.contains("jdbc.user=" + USER + "\n"));
        assertTrue(content.contains("jdbc.password=" + PASSWORD + "\n"));

        assertProgress("bitbucket.backup.configuration", 100, step.getProgress());

        verify(zipStream).putNextEntry(any(ZipEntry.class), anyBoolean());
        verify(zipStream, atLeastOnce()).closeEntry();
    }

    @Test
    public void testRunNoJdbcPropertiesInPresentConfigFile() throws Exception {
        createConfigFile();

        assertProgress("bitbucket.backup.configuration", 0, step.getProgress());

        step.run();

        String content = getContent();

        assertTrue(content.startsWith(CONTENT + "\n" + ConfigurationBackupStep.BANNER + "\n"));
        assertTrue(content.contains("jdbc.driver=" + DRIVER_CLASS_NAME + "\n"));
        assertTrue(content.contains("jdbc.url=" + URL + "\n"));
        assertTrue(content.contains("jdbc.user=" + USER + "\n"));
        assertTrue(content.contains("jdbc.password=" + PASSWORD + "\n"));

        assertProgress("bitbucket.backup.configuration", 100, step.getProgress());

        verify(zipStream).putNextEntry(any(ZipEntry.class), anyBoolean());
        verify(zipStream, atLeastOnce()).closeEntry();
    }

    @Test
    public void testRunWithJdbcPropertiesInConfigFile() throws Exception {
        createConfigFile(CONTENT + "\n", "jdbc.driver=testRunWithJdbcPropertiesInConfigFile\n",
                "jdbc.url=testRunWithJdbcPropertiesInConfigFile\n",
                "jdbc.user=testRunWithJdbcPropertiesInConfigFile\n",
                "jdbc.password=testRunWithJdbcPropertiesInConfigFile\n");

        assertProgress("bitbucket.backup.configuration", 0, step.getProgress());
        step.run();

        String content = getContent();
        assertTrue(content.startsWith(CONTENT + "\n"));
        assertFalse(content.contains(ConfigurationBackupStep.BANNER));
        assertTrue(content.contains("jdbc.driver=testRunWithJdbcPropertiesInConfigFile\n"));
        assertTrue(content.contains("jdbc.url=testRunWithJdbcPropertiesInConfigFile\n"));
        assertTrue(content.contains("jdbc.user=testRunWithJdbcPropertiesInConfigFile\n"));
        assertTrue(content.contains("jdbc.password=testRunWithJdbcPropertiesInConfigFile\n"));

        assertProgress("bitbucket.backup.configuration", 100, step.getProgress());

        verify(zipStream).putNextEntry(any(ZipEntry.class), anyBoolean());
        verify(zipStream, atLeastOnce()).closeEntry();
    }

    private void createConfigFile() {
        createConfigFile(CONTENT);
    }

    private void createConfigFile(String...lines) {
        StringBuilder builder = new StringBuilder();
        for (String line : lines) {
            builder.append(line);
        }
        try {
            FileUtils.write(new File(tmpDir.getRoot(), ApplicationConstants.CONFIG_FILE), builder.toString());
        } catch (IOException e) {
            fail("Error while writing test config file " + e);
        }
    }

    private String getContent() throws IOException {
        ZipInputStream inputStream = new ZipInputStream(new ByteArrayInputStream(output.toByteArray()));
        try {
            inputStream.getNextEntry();
            return IOUtils.toString(inputStream);
        } finally {
            Closeables.closeQuietly(inputStream);
        }
    }
}
