package com.atlassian.stash.internal.db;

import com.atlassian.bitbucket.cluster.ClusterService;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.stash.internal.backup.MigrationTarget;
import com.atlassian.bitbucket.util.Version;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.jdbc.CannotGetJdbcConnectionException;

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

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class DefaultDatabaseValidatorTest {

    @Mock
    private ClusterService clusterService;
    @Mock
    private Connection connection;
    @Mock
    private DetailedDatabase database;
    @Mock
    private DataSource dataSource;
    @Mock
    private DatabaseSupplier databaseSupplier;
    @Spy
    private I18nService i18nService = new SimpleI18nService();
    @Mock
    private MigrationTarget migrationTarget;
    private DefaultDatabaseValidator validator;
    
    @Before
    public void setup() throws SQLException {
        when(dataSource.getConnection()).thenReturn(connection);
        when(databaseSupplier.getForConnection(same(connection))).thenReturn(database);

        validator = new DefaultDatabaseValidator(clusterService, databaseSupplier, i18nService) {
            
            @Override
            protected MigrationTarget createMigrationTarget(Connection connection) {
                return migrationTarget;
            }
        };
    }

    @Test
    public void testValidate() throws SQLException {
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.SUPPORTED);
        when(migrationTarget.hasNoClashingTables()).thenReturn(true);
        when(migrationTarget.hasRequiredSchemaPermissions()).thenReturn(true);
        when(migrationTarget.hasRequiredTemporaryTablePermission()).thenReturn(true);
        when(migrationTarget.isCaseSensitive()).thenReturn(true);
        when(migrationTarget.isUtf8()).thenReturn(true);

        validator.validate(dataSource);

        verify(clusterService).isAvailable();
        verify(database).getSupportLevel();
        verify(database, never()).isClusterable();
        verify(migrationTarget).hasNoClashingTables();
        verify(migrationTarget).hasRequiredSchemaPermissions();
        verify(migrationTarget).hasRequiredTemporaryTablePermission();
        verify(migrationTarget).isCaseSensitive();
        verify(migrationTarget).isUtf8();
        verify(connection).close();
    }

    @Test
    public void testValidateIfDataCenterAndClusterable() throws SQLException {
        when(clusterService.isAvailable()).thenReturn(true);
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.SUPPORTED);
        when(database.isClusterable()).thenReturn(true);
        when(migrationTarget.hasNoClashingTables()).thenReturn(true);
        when(migrationTarget.hasRequiredSchemaPermissions()).thenReturn(true);
        when(migrationTarget.hasRequiredTemporaryTablePermission()).thenReturn(true);
        when(migrationTarget.isCaseSensitive()).thenReturn(true);
        when(migrationTarget.isUtf8()).thenReturn(true);

        validator.validate(dataSource);

        verify(clusterService).isAvailable();
        verify(database).getSupportLevel();
        verify(database).isClusterable();
        verify(migrationTarget).hasNoClashingTables();
        verify(migrationTarget).hasRequiredSchemaPermissions();
        verify(migrationTarget).hasRequiredTemporaryTablePermission();
        verify(migrationTarget).isCaseSensitive();
        verify(migrationTarget).isUtf8();
        verify(connection).close();
    }

    @Test(expected = DatabaseValidationException.class)
    public void testValidateIfDataCenterAndNotClusterable() throws SQLException {
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.SUPPORTED);
        when(clusterService.isAvailable()).thenReturn(true);

        try {
            validator.validate(dataSource);
        } finally {
            verify(clusterService).isAvailable();
            verify(connection).close();
        }
    }

    @Test(expected = DatabaseValidationException.class)
    public void testValidateIfHasClashingTables() throws SQLException {
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.SUPPORTED);
        when(migrationTarget.isUtf8()).thenReturn(true);

        try {
            validator.validate(dataSource);
        } finally {
            verify(clusterService).isAvailable();
            verify(migrationTarget).isUtf8();
            verify(migrationTarget).hasNoClashingTables();
            verify(connection).close();
        }
    }

    @Test(expected = DatabaseValidationException.class)
    public void testValidateIfLacksPermissions() throws SQLException {
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.SUPPORTED);
        when(migrationTarget.hasNoClashingTables()).thenReturn(true);
        when(migrationTarget.isUtf8()).thenReturn(true);

        try {
            validator.validate(dataSource);
        } finally {
            verify(clusterService).isAvailable();
            verify(migrationTarget).hasNoClashingTables();
            verify(migrationTarget).hasRequiredSchemaPermissions();
            verify(migrationTarget).isUtf8();
            verify(connection).close();
        }
    }

    @Test(expected = DatabaseValidationException.class)
    public void testValidateIfLacksTemporaryTablePermissions() throws SQLException {
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.SUPPORTED);
        when(migrationTarget.hasNoClashingTables()).thenReturn(true);
        when(migrationTarget.hasRequiredSchemaPermissions()).thenReturn(true);
        when(migrationTarget.isUtf8()).thenReturn(true);

        try {
            validator.validate(dataSource);
        } finally {
            verify(clusterService).isAvailable();
            verify(migrationTarget).hasNoClashingTables();
            verify(migrationTarget).hasRequiredSchemaPermissions();
            verify(migrationTarget).hasRequiredTemporaryTablePermission();
            verify(migrationTarget).isUtf8();
            verify(connection).close();
        }
    }

    @Test(expected = DatabaseValidationException.class)
    public void testValidateIfNotCaseSensitive() throws SQLException {
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.SUPPORTED);
        when(migrationTarget.hasNoClashingTables()).thenReturn(true);
        when(migrationTarget.hasRequiredSchemaPermissions()).thenReturn(true);
        when(migrationTarget.hasRequiredTemporaryTablePermission()).thenReturn(true);
        when(migrationTarget.isUtf8()).thenReturn(true);

        try {
            validator.validate(dataSource);
        } finally {
            verify(clusterService).isAvailable();
            verify(migrationTarget).hasNoClashingTables();
            verify(migrationTarget).hasRequiredSchemaPermissions();
            verify(migrationTarget).hasRequiredTemporaryTablePermission();
            verify(migrationTarget).isCaseSensitive();
            verify(migrationTarget).isUtf8();
            verify(connection).close();
        }
    }

    @Test(expected = DatabaseValidationException.class)
    public void testValidateIfNotUtf8() throws SQLException {
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.SUPPORTED);

        try {
            validator.validate(dataSource);
        } finally {
            verify(clusterService).isAvailable();
            verify(migrationTarget).isUtf8();
            verify(connection).close();
        }
    }

    @Test(expected = DatabaseValidationException.class)
    public void testValidateIfUnsupportedDatabase() throws SQLException {
        when(database.getName()).thenReturn("MySQL");
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.UNSUPPORTED);
        when(database.getVersion()).thenReturn(new Version(5, 6, 10));

        try {
            validator.validate(dataSource);
        } finally {
            verify(database).getSupportLevel();
            verify(databaseSupplier).getForConnection(same(connection));
            verify(dataSource).getConnection();
        }
    }

    @Test(expected = CannotGetJdbcConnectionException.class)
    public void testValidatePropagatesCannotGetJdbcConnectionException() throws SQLException {
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.SUPPORTED);

        reset(dataSource);
        doThrow(CannotGetJdbcConnectionException.class).when(dataSource).getConnection();

        try {
            validator.validate(dataSource);
        } finally {
            verify(dataSource).getConnection();
        }
    }

    //Verifies that validate(DataSource) doesn't NullPointerException if the ClusterService is not set
    @Test
    public void testValidateWithoutClusterService() throws SQLException {
        when(database.getSupportLevel()).thenReturn(DatabaseSupportLevel.SUPPORTED);
        when(migrationTarget.hasNoClashingTables()).thenReturn(true);
        when(migrationTarget.hasRequiredSchemaPermissions()).thenReturn(true);
        when(migrationTarget.hasRequiredTemporaryTablePermission()).thenReturn(true);
        when(migrationTarget.isCaseSensitive()).thenReturn(true);
        when(migrationTarget.isUtf8()).thenReturn(true);

        new DefaultDatabaseValidator(databaseSupplier, i18nService) {

            @Override
            protected MigrationTarget createMigrationTarget(Connection connection) {
                return migrationTarget;
            }
        }.validate(dataSource);

        verify(database).getSupportLevel();
        verify(database, never()).isClusterable();
        verify(migrationTarget).hasNoClashingTables();
        verify(migrationTarget).hasRequiredSchemaPermissions();
        verify(migrationTarget).hasRequiredTemporaryTablePermission();
        verify(migrationTarget).isCaseSensitive();
        verify(migrationTarget).isUtf8();
        verify(connection).close();
    }
}
