package com.atlassian.bitbucket.mesh;

import com.atlassian.bitbucket.mesh.layout.StorageLayout;
import com.atlassian.bitbucket.mesh.liquibase.ExtendedSpringLiquibase;
import com.atlassian.bitbucket.mesh.repository.JdbcRepositoryAttributeDao;
import com.atlassian.bitbucket.mesh.repository.JdbcWorkTreeStateDao;
import com.atlassian.bitbucket.mesh.repository.RepositoryAttributeDao;
import com.atlassian.bitbucket.mesh.repository.WorkTreeStateDao;
import com.atlassian.bitbucket.mesh.util.ConcurrencyUtils;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import liquibase.integration.spring.SpringLiquibase;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.JdbcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@EnableConfigurationProperties({DatabasePoolProperties.class, JdbcProperties.class})
@EnableTransactionManagement
public class MeshDatabaseWiring {

    @Bean(initMethod = "validate")
    @ConditionalOnMissingBean //to let the tests override this
    @DependsOn("incompatibleDatabaseResetter")
    public H2DatabaseValidator databaseValidator(StorageLayout storageLayout) {
        return new H2DatabaseValidator(storageLayout);
    }

    @Bean
    @ConditionalOnMissingBean //to let the tests override this
    public DataSourceProperties dataSourceProperties(StorageLayout storageLayout) {
        //The JDBC URL is hard-coded at runtime to ensure adventurous customers don't attempt to use
        //an external database--something that would be possible if we used Spring Boot's autoconfig
        String url = "jdbc:h2:" + storageLayout.getHomeDir() + "/mesh;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE";

        return new DataSourceProperties(url, "sa", "");
    }

    @Bean
    @DependsOn("databaseValidator")
    public DataSource dataSource(DatabasePoolProperties poolProperties, DataSourceProperties sourceProperties,
                                 @Value("${jmx.enabled}") boolean jmxEnabled) {
        HikariConfig config = new HikariConfig();
        config.setAutoCommit(true); //Anything using an ad hoc connection outside a transaction will be auto-commit
        config.setConnectionTimeout(poolProperties.getConnectTimeout().toMillis());
        config.setJdbcUrl(sourceProperties.getUrl());
        config.setIdleTimeout(poolProperties.getIdleTimeout().toMillis());
        config.setLeakDetectionThreshold(poolProperties.getLeakTimeout().toMillis());
        config.setMaxLifetime(poolProperties.getLifetimeTimeout().toMillis());
        config.setMaximumPoolSize(poolProperties.getMaxSize());
        config.setMinimumIdle(poolProperties.getIdleSize());
        config.setPassword(sourceProperties.getPassword());
        config.setPoolName("bitbucket-mesh");
        config.setRegisterMbeans(jmxEnabled);
        config.setTransactionIsolation("TRANSACTION_READ_COMMITTED");
        config.setThreadFactory(ConcurrencyUtils.createDaemonThreadFactory("hikari"));
        config.setUsername(sourceProperties.getUser());

        return new TransactionAwareDataSourceProxy(new HikariDataSource(config));
    }

    @Bean(initMethod = "mayReset")
    @ConditionalOnMissingBean //to let the tests override this
    public IncompatibleSidecarDatabaseResetter incompatibleDatabaseResetter(Environment environment,
                                                                            StorageLayout storageLayout) {
        return new IncompatibleSidecarDatabaseResetter(environment, storageLayout);
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        JdbcProperties.Template template = properties.getTemplate();
        jdbcTemplate.setFetchSize(template.getFetchSize());
        jdbcTemplate.setMaxRows(template.getMaxRows());
        if (template.getQueryTimeout() != null) {
            jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
        }
        return jdbcTemplate;
    }

    @Bean
    public SpringLiquibase liquibase(DataSource dataSource) {
        return new ExtendedSpringLiquibase("classpath:liquibase/master.xml", dataSource);
    }

    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
        return new NamedParameterJdbcTemplate(jdbcTemplate);
    }

    @Bean
    public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public RepositoryAttributeDao repositoryAttributeDao(JdbcTemplate jdbcTemplate) {
        return new JdbcRepositoryAttributeDao(jdbcTemplate);
    }

    @Bean
    public WorkTreeStateDao workTreeStateDao(JdbcTemplate template) {
        return new JdbcWorkTreeStateDao(template);
    }
}
