import java.lang.invoke.MethodHandles;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Duration;
import java.util.Collections;
import javax.sql.DataSource;

import exception.AppInitializeException;
import liquibase.integration.spring.SpringLiquibase;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import net.javacrumbs.shedlock.spring.ScheduledLockConfiguration;
import net.javacrumbs.shedlock.spring.ScheduledLockConfigurationBuilder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

/**
 * Root application context
 * This context imports XML configs from all the other jars, and is created by {@link WebAppInitializer}
 * NB: modules are excluded from component scan since spring-context.xml sometimes holds important parameters and / or annotations
 * @author krylov
 */
@Configuration
@ComponentScan(basePackages = {
    "service",
    "dao",
    "bpmn",
    "i18n",
    "errorhandling",
    "database",
    "component.addresses",
    "gen",
    "ru.cg",
    "ru.micord",
    "ervu_business_metrics"
}, excludeFilters = {
    @ComponentScan.Filter(type = FilterType.REGEX, pattern = "security.WebSecurityConfig"),
    @ComponentScan.Filter(type = FilterType.REGEX, pattern = "ru.cg.webbpm.modules.database.impl.DatabaseConfiguration"),
})
@EnableAspectJAutoProxy(proxyTargetClass = true)
@EnableWebMvc
@EnableScheduling
public class AppConfig {

  private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  @Bean
  public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
    return new PropertySourcesPlaceholderConfigurer();
  }

  @Bean
  public ScheduledLockConfiguration taskScheduler(LockProvider lockProvider) {
    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
    scheduler.setPoolSize(12);
    scheduler.initialize();
    return ScheduledLockConfigurationBuilder
        .withLockProvider(lockProvider)
        .withTaskScheduler(scheduler)
        .withDefaultLockAtMostFor(Duration.ofHours(4))
        .build();
  }

  @Bean
  public LockProvider lockProvider(@Qualifier("datasource") DataSource dataSource) {
    return new JdbcTemplateLockProvider(dataSource, "ervu_business_metrics.shedlock");
  }

  @Bean
  public SpringLiquibase liquibase(@Qualifier("datasource") DataSource dataSource) {
    SpringLiquibase liquibase = new SpringLiquibase();
    createErvuMetricsSchema(dataSource);
    liquibase.setDataSource(dataSource);
    liquibase.setChangeLog("classpath:config/changelog-master.xml");
    liquibase.setChangeLogParameters(Collections.singletonMap("projectUser", getProjectDbUserName(dataSource)));
    liquibase.setDefaultSchema("ervu_business_metrics");
    return liquibase;
  }

  private String getProjectDbUserName(DataSource dataSource) {
    try (Connection connection = dataSource.getConnection()) {
      return connection.getMetaData().getUserName();
    }
    catch (SQLException e) {
      throw new AppInitializeException(e);
    }
  }

  private void createErvuMetricsSchema(DataSource dataSource) {
    try (Connection connection = dataSource.getConnection()) {
      LOGGER.info("Start creating ervu_business_metrics schema");
      String sql = "CREATE SCHEMA IF NOT EXISTS ervu_business_metrics;";
      try (Statement statement = connection.createStatement()) {
        connection.setAutoCommit(false);
        statement.execute(sql);
        connection.commit();
        LOGGER.info("ervu_business_metrics schema was created");
      }
      catch (SQLException e) {
        connection.rollback();
        throw new AppInitializeException(e);
      }
    }
    catch (SQLException e) {
      throw new AppInitializeException(e);
    }
  }

  @Bean
  public RestTemplate restTemplate() {
    return new RestTemplate();
  }

}
