package com.atlassian.stash.internal.jdbc;

import com.atlassian.stash.internal.db.DbType;
import com.atlassian.stash.internal.home.HomeDirectoryDetails;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.File;
import java.io.IOException;
import java.util.regex.Matcher;

import static com.atlassian.stash.internal.jdbc.DefaultDataSourceConfigurationFactory.*;
import static org.junit.Assert.*;

public class DefaultDataSourceConfigurationFactoryTest {

    @Rule
    public TemporaryFolder folder = new TemporaryFolder();

    private DefaultDataSourceConfigurationFactory factory;
    private HomeDirectoryDetails homeDirectory;

    @Before
    public void setup() throws IOException {
        homeDirectory = new HomeDirectoryDetails(folder.getRoot(), folder.newFolder("shared"));

        factory = new DefaultDataSourceConfigurationFactory(homeDirectory);
    }

    @Test
    public void testCreateFallsBackToHsql() throws IOException {
        File dataDir = folder.newFolder("shared", "data");
        assertTrue(new File(dataDir, "db.script").createNewFile());

        DataSourceConfiguration configuration = factory.create(H2_DRIVER, "jdbc:h2:mem:test", "user", "password");
        assertEquals(HSQL_DRIVER, configuration.getDriverClassName());
        assertEquals("", configuration.getPassword());
        assertEquals(String.format(HSQL_URL, homeDirectory.getSharedHomeDir()), configuration.getUrl());
        assertEquals("SA", configuration.getUser());
    }

    @Test
    public void testCreatePrefersH2OverHsqlWhenBothArePresent() throws IOException {
        File dataDir = folder.newFolder("shared", "data");
        assertTrue(new File(dataDir, "db.script").createNewFile()); //HSQL
        assertTrue(new File(dataDir, "db.h2.db").createNewFile());  //H2

        DataSourceConfiguration configuration = factory.create(H2_DRIVER, "jdbc:h2:mem:test", "user", "password");
        assertEquals(H2_DRIVER, configuration.getDriverClassName());
        assertEquals("password", configuration.getPassword());
        assertEquals("jdbc:h2:mem:test", configuration.getUrl());
        assertEquals("user", configuration.getUser());
    }

    @Test
    public void testCreateReplacesGeneratedJtdsUrlWithMicrosoft() {
        DataSourceConfiguration configuration = factory.create(JTDS_DRIVER,
                JTDS_PROTOCOL + "://example.com:6098;databaseName=stash;", "user", "password");
        assertEquals(DbType.MSSQL.getDriverClassName(), configuration.getDriverClassName());
        assertEquals(DbType.MSSQL.getProtocol() + "://example.com:6098;databaseName=stash;", configuration.getUrl());
        assertEquals("password", configuration.getPassword());
        assertEquals("user", configuration.getUser());
    }

    @Test
    public void testCreateReplacesAlternativeJtdsUrlWithMicrosoft() {
        DataSourceConfiguration configuration = factory.create(JTDS_DRIVER,
                JTDS_PROTOCOL + "://example.com:6098/stash", "user", "password");
        assertEquals(DbType.MSSQL.getDriverClassName(), configuration.getDriverClassName());
        assertEquals(DbType.MSSQL.getProtocol() + "://example.com:6098;databaseName=stash;", configuration.getUrl());
        assertEquals("password", configuration.getPassword());
        assertEquals("user", configuration.getUser());
    }

    @Test(expected = UnsupportedJdbcUrlException.class)
    public void testJtdsTranslationThrowsOnRedundantDatabaseNames() {
        factory.create(JTDS_DRIVER, JTDS_PROTOCOL + "://example.com:6098/stash;databaseName=stash", "user", "password");
    }

    @Test(expected = UnsupportedJdbcUrlException.class)
    public void testJtdsTranslationThrowsOnUnmatchedJtdsUrls() {
        factory.create(JTDS_DRIVER, "jdbc:postgresql://localhost:5432/stash", "user", "password");
    }

    @Test(expected = UnsupportedJdbcUrlException.class)
    public void testJtdsTranslationThrowsOnUnsupportedJtdsUrls() {
        factory.create(JTDS_DRIVER, JTDS_PROTOCOL + "://example.com:6098;databaseName=stash;domain=NA;", "user", "password");
    }

    @Test
    public void testJtdsUrlPattern() {
        assertMatch(JTDS_PROTOCOL + "://localhost:1433;databaseName=stash;", "localhost", "1433", null, ";databaseName=stash;");
        assertMatch(JTDS_PROTOCOL + "://localhost:1433;databaseName=stash", "localhost", "1433", null, ";databaseName=stash");
        assertMatch(JTDS_PROTOCOL + "://localhost;databaseName=stash;", "localhost", null, null, ";databaseName=stash;");
        assertMatch(JTDS_PROTOCOL + "://localhost;databaseName=stash", "localhost", null, null, ";databaseName=stash");
        assertMatch(JTDS_PROTOCOL + "://localhost:1433/stash;", "localhost", "1433", "stash", ";");
        assertMatch(JTDS_PROTOCOL + "://localhost:1433/stash", "localhost", "1433", "stash", null);
        assertMatch(JTDS_PROTOCOL + "://localhost/stash;", "localhost", null, "stash", ";");
        assertMatch(JTDS_PROTOCOL + "://localhost/stash", "localhost", null, "stash", null);
        assertMatch(JTDS_PROTOCOL + "://localhost:1433", "localhost", "1433", null, null);
        assertMatch(JTDS_PROTOCOL + "://localhost", "localhost", null, null, null);
        assertNoMatch(JTDS_PROTOCOL + "://localhost:;databaseName=stash");
        assertNoMatch(JTDS_PROTOCOL + "://localhost:/stash");
        assertNoMatch(JTDS_PROTOCOL + "://");
        assertNoMatch("jdbc:postgresql://localhost:5432/stash");
    }

    private static void assertMatch(String value, String host, String port, String databaseName, String parameters) {
        Matcher matcher = JTDS_URL.matcher(value);
        assertTrue(value + " did not match the URL pattern where a match was expected", matcher.matches());
        assertEquals("Unexpected host extracted for [" + value + "]", host, matcher.group(1));
        assertEquals("Unexpected port extracted for [" + value + "]", port, matcher.group(2));
        assertEquals("Unexpected database name extracted for [" + value + "]", databaseName, matcher.group(3));
        assertEquals("Unexpected parameters extracted for [" + value + "]", parameters, matcher.group(4));
    }

    private static void assertNoMatch(String value) {
        Matcher matcher = JTDS_URL.matcher(value);
        if (matcher.matches()) {
            fail(value + " matched the URL pattern where a match was not expected.");
        }
    }
}
