package com.atlassian.stash.internal.config;

import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.stash.internal.db.DatabaseConstants;
import com.atlassian.stash.internal.jdbc.DataSourceConfiguration;
import com.google.common.base.Splitter;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Iterator;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.core.IsCollectionContaining.hasItem;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;

/**
 * Tests the operation of the {@link DataSourceConfigurationAmendment} class.
 */
@RunWith(MockitoJUnitRunner.class)
public class DataSourceConfigurationAmendmentTest {

    private static final String JDBC_URL = "jdbc:stash://localhost:7990/bitbucket";
    private static final String JDBC_DRIVER = "com.atlassian.stash.NoJdbcDriver";
    private static final String JDBC_USER = "username";
    private static final String JDBC_PASSWORD = "s3cr3t";

    private static final String PROPERTY_LINE = String.format("%s=%s", DatabaseConstants.PROP_JDBC_DRIVER, JDBC_DRIVER);

    @Mock
    private Clock clock;
    @Mock
    private DataSourceConfiguration dataSourceConfig;
    @Mock
    private ApplicationUser user;

    @Before
    public void setup() {
        when(clock.now()).thenReturn(new DateTime(2004, 12, 25, 12, 0, 0, 0, DateTimeZone.UTC));

        when(dataSourceConfig.getUrl()).thenReturn(JDBC_URL);
        when(dataSourceConfig.getDriverClassName()).thenReturn(JDBC_DRIVER);
        when(dataSourceConfig.getUser()).thenReturn(JDBC_USER);
        when(dataSourceConfig.getPassword()).thenReturn(JDBC_PASSWORD);

        when(user.getId()).thenReturn(42);
        when(user.getDisplayName()).thenReturn("Joe Bloggs");
        when(user.isActive()).thenReturn(true);
    }

    @Test
    public void testFinalize() throws IOException {
        String message = "It's happening!";
        DataSourceConfigurationAmendment amendment = new DataSourceConfigurationAmendment(dataSourceConfig,
                message, clock, user);

        StringWriter writer = new StringWriter();
        amendment.finalize(writer);
        assertOutput(user, message, writer.toString());
    }

    @Test
    public void testWithUser() throws IOException {
        String message = "Migration of database from HSQL to MySQL";
        DataSourceConfigurationAmendment amendment = new DataSourceConfigurationAmendment(dataSourceConfig,
                message, clock, user);

        StringWriter writer = new StringWriter();
        amendment.amend(writer, PROPERTY_LINE);
        assertOutput(user, message, writer.toString());
    }

    @Test
    public void testWithoutUser() throws IOException {
        String message = "Migration of database from HSQL to MySQL";
        DataSourceConfigurationAmendment amendment = new DataSourceConfigurationAmendment(dataSourceConfig,
                message, clock, null);

        StringWriter writer = new StringWriter();
        amendment.amend(writer, PROPERTY_LINE);
        assertOutput(message, writer.toString());
    }

    //The properties code considers \ and escape character, so strings like "some.host\instance" become corrupted
    //if the backslash is not escaped when the value is written
    @Test
    public void testWriteToEscapesBackslashes() throws IOException {
        reset(dataSourceConfig);

        when(dataSourceConfig.getUrl()).thenReturn("jdbc:stash://localhost\\instance:7990/bitbucket");
        when(dataSourceConfig.getDriverClassName()).thenReturn("com\\atlassian\\stash\\NoJdbcDriver");
        when(dataSourceConfig.getUser()).thenReturn("na\\user");
        when(dataSourceConfig.getPassword()).thenReturn("abc123!@#/\\");

        DataSourceConfigurationAmendment amendment = new DataSourceConfigurationAmendment(
                dataSourceConfig, "It's happening!", clock, user);

        StringWriter writer = new StringWriter();
        amendment.amend(writer, PROPERTY_LINE);
        Iterable<String> lines = Splitter.on('\n').trimResults().omitEmptyStrings().split(writer.toString());

        assertThat(lines, hasItem("jdbc.driver=com\\\\atlassian\\\\stash\\\\NoJdbcDriver"));
        assertThat(lines, hasItem("jdbc.url=jdbc:stash://localhost\\\\instance:7990/bitbucket"));
        assertThat(lines, hasItem("jdbc.user=na\\\\user"));
        assertThat(lines, hasItem("jdbc.password=abc123!@#/\\\\"));
    }

    private void assertOutput(String message, String output) {
        assertOutput(null, message, output);
    }

    private void assertOutput(ApplicationUser user, String message, String output) {
        Iterable<String> lines = Splitter.on('\n').trimResults().omitEmptyStrings().split(output);
        Iterator<String> iterator = lines.iterator();

        assertEquals("#>*******************************************************", iterator.next());
        assertEquals(String.format("#> %s", message), iterator.next());

        if (user == null) {
            assertEquals("#> Updated on 2004-12-25T12:00:00.000Z", iterator.next());
        } else {
            assertEquals("#> Updated by Joe Bloggs on 2004-12-25T12:00:00.000Z", iterator.next());
        }

        assertEquals("#>*******************************************************", iterator.next());

        assertThat(iterator.next(), equalTo(String.format("%s=%s", DatabaseConstants.PROP_JDBC_DRIVER, JDBC_DRIVER)));
        assertThat(iterator.next(), equalTo(String.format("%s=%s", DatabaseConstants.PROP_JDBC_URL, JDBC_URL)));
        assertThat(iterator.next(), equalTo(String.format("%s=%s", DatabaseConstants.PROP_JDBC_USER, JDBC_USER)));
        assertThat(iterator.next(), equalTo(String.format("%s=%s", DatabaseConstants.PROP_JDBC_PASSWORD, JDBC_PASSWORD)));

        //If the iterator has any lines after the _active_ configuration, verify that they're commented out
        while (iterator.hasNext()) {
            assertEquals(String.format("# %s", PROPERTY_LINE), iterator.next());
        }
    }
}
