package com.atlassian.stash.internal.db;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.jdbc.CannotGetJdbcConnectionException;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class DefaultDatabaseSupplierMockTest {

    @Mock
    private Connection connection;
    @Mock
    private DataSource dataSource;
    @Mock
    private DatabaseMetaData metaData;
    @InjectMocks
    private DefaultDatabaseSupplier supplier;

    @Test
    public void testGetForUnknownDatabaseReturnsUnsupported() throws SQLException {
        when(connection.getMetaData()).thenReturn(metaData);

        when(dataSource.getConnection()).thenReturn(connection);

        //These values are drawn from Derby's EmbedDatabaseMetadata -> ProductVersionInfo -> DBMS.properties
        when(metaData.getDatabaseProductName()).thenReturn("Apache Derby");
        when(metaData.getDatabaseProductVersion()).thenReturn("10.11.1.1");
        when(metaData.getDatabaseMajorVersion()).thenReturn(10);
        when(metaData.getDatabaseMinorVersion()).thenReturn(11);

        DetailedDatabase database = supplier.get();
        assertNotNull(database);
        assertEquals(10, database.getMajorVersion());
        assertEquals(11, database.getMinorVersion());
        assertEquals("Apache Derby", database.getName());
        assertEquals(1, database.getPatchVersion());
        assertEquals("10.11.1.1", database.getVersion().toString());
        assertSame(DatabaseSupportLevel.UNSUPPORTED, database.getSupportLevel());
    }

    @Test(expected = CannotGetJdbcConnectionException.class)
    public void testGetUnwrapsInitializationExceptions() throws SQLException {
        doThrow(SQLException.class).when(dataSource).getConnection();

        try {
            supplier.get();
        } finally {
            verify(dataSource).getConnection();
        }
    }

    @Test(expected = DataRetrievalFailureException.class)
    public void testGetForConnectionHandlesExceptionFromMetadata() throws SQLException {
        doThrow(SQLException.class).when(connection).getMetaData();

        when(dataSource.getConnection()).thenReturn(connection);

        try {
            supplier.getForConnection(connection);
        } finally {
            verify(connection).getMetaData();
        }
    }

    @Test
    public void testParseVersionHandlesMultiLineOracleVersions() throws SQLException {
        when(connection.getMetaData()).thenReturn(metaData);

        when(dataSource.getConnection()).thenReturn(connection);

        when(metaData.getDatabaseProductName()).thenReturn("Oracle");
        when(metaData.getDatabaseProductVersion()).thenReturn("Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production\n" +
                "With the Partitioning, Automatic Storage Management and Oracle Label Security options");
        when(metaData.getDatabaseMajorVersion()).thenReturn(11);
        when(metaData.getDatabaseMinorVersion()).thenReturn(2);

        DetailedDatabase database = supplier.get();
        assertNotNull(database);
        assertEquals(11, database.getMajorVersion());
        assertEquals(2, database.getMinorVersion());
        assertEquals("Oracle", database.getName());
        assertEquals(0, database.getPatchVersion());
        assertEquals("11.2.0.3.0", database.getVersion().toString());
        assertSame(DatabaseSupportLevel.SUPPORTED, database.getSupportLevel());
    }

    @Test
    public void testParseVersionHandlesUnexpectedOracleVersions() throws SQLException {
        when(connection.getMetaData()).thenReturn(metaData);

        when(dataSource.getConnection()).thenReturn(connection);

        when(metaData.getDatabaseProductName()).thenReturn("Oracle");
        when(metaData.getDatabaseProductVersion()).thenReturn("12.0.1");
        when(metaData.getDatabaseMajorVersion()).thenReturn(11); //Something like this could never happen, but this
        when(metaData.getDatabaseMinorVersion()).thenReturn(2);  //makes validation much easier

        DetailedDatabase database = supplier.get();
        assertNotNull(database);
        assertEquals(11, database.getMajorVersion()); //Verifies that the version string is _ignored_
        assertEquals(2, database.getMinorVersion());
        assertEquals("Oracle", database.getName());
        assertEquals(0, database.getPatchVersion());
        assertEquals("11.2.0", database.getVersion().toString()); //Version pads to 3 places
        assertSame(DatabaseSupportLevel.SUPPORTED, database.getSupportLevel());
    }

    @Test
    public void testValidateAllowsDatabaseWithUnknownSupport() throws SQLException {
        when(connection.getMetaData()).thenReturn(metaData);

        when(dataSource.getConnection()).thenReturn(connection);

        when(metaData.getDatabaseProductName()).thenReturn("Oracle");
        when(metaData.getDatabaseProductVersion()).thenReturn("Oracle Database 10g Express Edition Release 10.2.0.2.0 - Production");
        when(metaData.getDatabaseMajorVersion()).thenReturn(10);
        when(metaData.getDatabaseMinorVersion()).thenReturn(2);

        supplier.validate();

        verify(connection).getMetaData();
        verify(connection).close();
        verify(dataSource).getConnection();
    }

    @Test
    public void testValidateHonorsIgnoreUnsupported() {
        supplier.setIgnoreUnsupported(true);
        supplier.validate();

        verifyZeroInteractions(dataSource);
    }

    @Test(expected = UnsupportedDatabaseException.class)
    public void testValidateThrowsOnUnsupportedDatabase() throws SQLException {
        when(connection.getMetaData()).thenReturn(metaData);

        when(dataSource.getConnection()).thenReturn(connection);

        when(metaData.getDatabaseProductName()).thenReturn("MySQL");
        when(metaData.getDatabaseProductVersion()).thenReturn("5.6.11");
        when(metaData.getDatabaseMajorVersion()).thenReturn(5);
        when(metaData.getDatabaseMinorVersion()).thenReturn(6);

        try {
            supplier.validate();
        } finally {
            verify(connection).getMetaData();
            verify(connection).close();
            verify(dataSource).getConnection();
        }
    }
}
