package com.loc.framework.autoconfigure.jdbc;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.common.base.Strings;
import com.loc.framework.autoconfigure.ConditionalOnPrefixProperty;
import com.loc.framework.autoconfigure.LocBaseAutoConfiguration;
import com.zaxxer.hikari.HikariDataSource;
import java.util.Optional;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator;
import net.sf.log4jdbc.sql.jdbcapi.DataSourceSpy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.aspectj.AnnotationTransactionAspect;

/**
 * Created on 2017/12/26.
 */
@Slf4j
@Configuration
@ConditionalOnPrefixProperty(prefix = "loc", value = LocDsProperties.class)
@ConditionalOnClass({
    DataSource.class, HikariDataSource.class, DataSourceSpy.class
})
public class LocDataSourceAutoConfiguration extends LocBaseAutoConfiguration implements
    BeanFactoryPostProcessor, EnvironmentAware, Ordered {

  private ConfigurableEnvironment environment;

  private static final String[] PROPERTIES_TO_COPY = {
      "log4jdbc.debug.stack.prefix",
      "log4jdbc.sqltiming.warn.threshold",
      "log4jdbc.sqltiming.error.threshold",
      "log4jdbc.dump.booleanastruefalse",
      "log4jdbc.dump.fulldebugstacktrace",
      "log4jdbc.dump.sql.maxlinelength",
      "log4jdbc.statement.warn",
      "log4jdbc.dump.sql.select",
      "log4jdbc.dump.sql.insert",
      "log4jdbc.dump.sql.update",
      "log4jdbc.dump.sql.delete",
      "log4jdbc.dump.sql.create",
      "log4jdbc.dump.sql.addsemicolon",
      "log4jdbc.auto.load.popular.drivers",
      "log4jdbc.drivers",
      "log4jdbc.trim.sql",
      "log4jdbc.trim.sql.extrablanklines",
  };

  @Override
  public void postProcessBeanFactory(
      ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    LocDsProperties locDsProperties = resolverSetting(LocDsProperties.class,
        this.environment.getPropertySources());
    initLog4Jdbc();
    locDsProperties.getDataSource().forEach(
        (name, properties) -> createBean(configurableListableBeanFactory, name, properties));
  }


  private void createBean(ConfigurableListableBeanFactory configurableListableBeanFactory,
      String prefixName, JdbcProperties jdbcProperties) {
    String jdbcUrl = jdbcProperties.getJdbcUrl();
    checkArgument(!Strings.isNullOrEmpty(jdbcUrl), prefixName + " url is null or empty");
    log.info("prefixName is {}, jdbc properties is {}", prefixName, jdbcProperties);

    HikariDataSource hikariDataSource = createHikariDataSource(jdbcProperties);
    DataSourceSpy dataSource = new DataSourceSpy(hikariDataSource);

    DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
    AnnotationTransactionAspect.aspectOf().setTransactionManager(transactionManager);

    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

    register(configurableListableBeanFactory, dataSource, prefixName + "DataSource",
        prefixName + "Ds");
    register(configurableListableBeanFactory, jdbcTemplate, prefixName + "JdbcTemplate",
        prefixName + "Jt");
    register(configurableListableBeanFactory, transactionManager, prefixName + "TransactionManager",
        prefixName + "Tx");
  }


  private HikariDataSource createHikariDataSource(JdbcProperties jdbcProperties) {
    HikariDataSource hikariDataSource = new HikariDataSource();
    hikariDataSource.setJdbcUrl(jdbcProperties.getJdbcUrl());
    hikariDataSource.setUsername(jdbcProperties.getUsername());
    hikariDataSource.setPassword(jdbcProperties.getPassword());

    JdbcPoolProperties jdbcPoolProperties = jdbcProperties.getJdbcPool();
    hikariDataSource.setAutoCommit(jdbcPoolProperties.isAutoCommit());
    hikariDataSource.setConnectionTimeout(jdbcPoolProperties.getConnectionTimeout());
    hikariDataSource.setIdleTimeout(jdbcPoolProperties.getIdleTimeout());
    hikariDataSource.setMaxLifetime(jdbcPoolProperties.getMaxLifetime());
    hikariDataSource.setMaximumPoolSize(jdbcPoolProperties.getMaximumPoolSize());
    hikariDataSource.setMinimumIdle(jdbcPoolProperties.getMinimumIdle());
    hikariDataSource
        .setInitializationFailTimeout(jdbcPoolProperties.getInitializationFailTimeout());
    hikariDataSource.setIsolateInternalQueries(jdbcPoolProperties.isIsolateInternalQueries());
    hikariDataSource.setReadOnly(jdbcPoolProperties.isReadOnly());
    hikariDataSource.setRegisterMbeans(jdbcPoolProperties.isRegisterMbeans());
    Optional.ofNullable(jdbcPoolProperties.getDriverClassName())
        .ifPresent(hikariDataSource::setDriverClassName);
    hikariDataSource.setValidationTimeout(jdbcPoolProperties.getValidationTimeout());
    hikariDataSource.setLeakDetectionThreshold(jdbcPoolProperties.getLeakDetectionThreshold());
    return hikariDataSource;
  }


  private void initLog4Jdbc() {
    for (final String property : PROPERTIES_TO_COPY) {
      if (this.environment.containsProperty(property)) {
        System.setProperty(property, this.environment.getProperty(property));
      }
    }
    System.setProperty("log4jdbc.spylogdelegator.name", this.environment
        .getProperty("log4jdbc.spylogdelegator.name", Slf4jSpyLogDelegator.class.getName()));
  }

  @Override
  public void setEnvironment(Environment environment) {
    this.environment = (ConfigurableEnvironment) environment;
  }

  @Override
  public int getOrder() {
    return Integer.MAX_VALUE - 1;
  }
}