/**
 * 
 */
package org.springblade.datasource;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springblade.datasource.constants.DbConstant;
import org.springblade.datasource.enums.DateFormatEnum;
import org.springblade.datasource.enums.DetailColumnTypeEnum;
import org.springblade.datasource.enums.FunctionMethodEnum;
import org.springblade.datasource.enums.ViewColumnTypeEnum;
import org.springblade.datasource.model.DatasourceDto;
import org.springblade.datasource.model.TableColumnDto;
import org.springblade.datasource.model.TableViewDto;
import org.springblade.datasource.util.DatasourceUtil;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author wookvn
 *
 */
@Slf4j
public abstract class AbstractGmetryDialect implements IGmetryDialect {

	protected final Map<ViewColumnTypeEnum, String> columnTypeMap = new ConcurrentHashMap<>();
	
	protected final Map<FunctionMethodEnum, String> functionMethodMap = new ConcurrentHashMap<>();
	
	protected final Map<DateFormatEnum, String> dateFormatMap = new ConcurrentHashMap<>();
	
	@Override
	public List<TableViewDto> listTableViewsByDatasource(DatasourceDto datasource) {
		return listTableViewsByDatasource(datasource, null);
	}
	
	@Override
	public List<TableColumnDto> listTableColumnsByTableViewName(DatasourceDto datasource, String tableViewName) {
		if(datasource == null) {
			return null;
		}
		List<TableColumnDto> list = new ArrayList<TableColumnDto>();
		try (Connection connection = DatasourceUtil.createConnection(datasource);){
			DatabaseMetaData metaData = connection.getMetaData();
			String schemaPattern = "%" + dbSchema(datasource) + "%";
			ResultSet rs = metaData.getColumns(null, schemaPattern, tableViewName, "%");
			ResultSet pkRs = metaData.getPrimaryKeys(null, schemaPattern, tableViewName);
			List<String> primaryKeyColumnList = new ArrayList<>();
			while(pkRs.next()) {
				primaryKeyColumnList.add(pkRs.getString("COLUMN_NAME"));
			}
			while(rs.next()) {
				TableColumnDto columnDto = new TableColumnDto();
				columnDto.setColumnComments(rs.getString("REMARKS"));
				columnDto.setDataType(rs.getString("TYPE_NAME"));
				columnDto.setColumnName(rs.getString("COLUMN_NAME"));
				columnDto.setColumnSize(rs.getInt("COLUMN_SIZE"));
				columnDto.setDecimalDigits(rs.getInt("DECIMAL_DIGITS"));
				columnDto.setColumnType(ViewColumnTypeEnum.resolve(rs.getInt("DATA_TYPE")));
				columnDto.setDetailColumnType(DetailColumnTypeEnum.resolve(rs.getInt("DATA_TYPE")));
				//IS_NULLABLE - YES/NO  NULLABLE - 1(允许为空)/2(不确定)/0(不允许为空)
				columnDto.setNullAbleStatus("yes".equalsIgnoreCase(rs.getString("IS_NULLABLE")) ? 1 : 0); 
				//是否主键
				columnDto.setPrimaryKeyStatus(primaryKeyColumnList.contains(rs.getString("COLUMN_NAME")) ? 1 : 0);
				//IS_AUTOINCREMENT YES/NO 是否自增
				if(primaryKeyColumnList.contains(rs.getString("COLUMN_NAME")) && "YES".equals(rs.getString("IS_AUTOINCREMENT"))) {
					columnDto.setAutoIncrementStatus(1);
				}
				list.add(columnDto);
			}
			pkRs.close();
			rs.close();
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		return list;
	}

	@Override
	public String getColumnType(ViewColumnTypeEnum columnType, Integer columnSize, Integer decimalDigits) {
		String value = columnTypeMap.get(columnType);
		if(StrUtil.isEmpty(value)) {
			throw new RuntimeException("数据类型不存在|"+columnType);
		}
		if(columnType == ViewColumnTypeEnum.STRING) {
			return String.format(value, columnSize == null || columnSize <= 0 ? DbConstant.DEFAULT_COLUMN_STRING_SIZE : columnSize);
		}
		if(columnType == ViewColumnTypeEnum.NUMBER) {
			columnSize = columnSize == null || columnSize <= 0 ? DbConstant.DEFAULT_COLUMN_NUMBER_SIZE : columnSize;
			decimalDigits = decimalDigits == null ? DbConstant.DEFAULT_COLUMN_NUMBER_DIGITS : decimalDigits;
			return String.format(value, columnSize, decimalDigits);
		}
		return value;
	}
	
	@Override
	public String formatDateTime(DateFormatEnum dateTimeFormat, String columnSql) {
		String value = dateFormatMap.get(dateTimeFormat);
		if(StrUtil.isEmpty(value)) {
			throw new RuntimeException("日期类型不存在|"+dateTimeFormat+"或不支持该格式转换");
		}
		return formatMethodFunction(FunctionMethodEnum.DATE_TO_STR, columnSql, value);
	}
	
	@Override
	public String formatMethodFunction(FunctionMethodEnum functionMethod, Object... args) {
		String value = functionMethodMap.get(functionMethod);
		if(StrUtil.isEmpty(value)) {
			throw new RuntimeException("函数方法不存在|"+functionMethod+"或不支持该函数方法");
		}
		return String.format(value, args);
	}
	
	@Override
	public String buildCreateViewSql(String viewName, String sqlStatement) {
		return String.format(DbConstant.CREATE_REPLACE_VIEW_SQL_FORMAT, viewName, sqlStatement);
	}
	
	@Override
	public String convertDataType(ViewColumnTypeEnum oldColumnType, ViewColumnTypeEnum newColumnType, String columnSql,
			DateFormatEnum dateFormat) {
		FunctionMethodEnum functionMethod = null;
		if(oldColumnType == ViewColumnTypeEnum.STRING && newColumnType == ViewColumnTypeEnum.DATETIME) {
			functionMethod = FunctionMethodEnum.STR_TO_DATE;
		}else if(oldColumnType == ViewColumnTypeEnum.NUMBER && newColumnType == ViewColumnTypeEnum.DATETIME) {
			functionMethod = FunctionMethodEnum.NUMBER_TO_DATE;
		}else if(oldColumnType == ViewColumnTypeEnum.DATETIME && newColumnType == ViewColumnTypeEnum.STRING) {
			functionMethod = FunctionMethodEnum.DATE_TO_STR;
		}
		if(oldColumnType == null || oldColumnType == newColumnType || functionMethod == null) {
			return columnSql;
		}
		String dateTimeFormat = "";
		if(dateFormat != null && (oldColumnType == ViewColumnTypeEnum.DATETIME || newColumnType == ViewColumnTypeEnum.DATETIME)) {
			dateTimeFormat = dateFormatMap.get(dateFormat);
			if(StrUtil.isEmpty(dateTimeFormat)) {
				throw new RuntimeException("日期类型不存在|"+dateTimeFormat+"或不支持日期格式");
			}
		}
		return formatMethodFunction(functionMethod, columnSql, dateTimeFormat);
	}

	@Override
	public List<TableViewDto> listTableViewsByDatasource(DatasourceDto datasource,String tableName) {
		if(datasource == null) {
			return null;
		}
		List<TableViewDto> list = new ArrayList<TableViewDto>();
		try (Connection connection = DatasourceUtil.createConnection(datasource);){
			DatabaseMetaData metaData = connection.getMetaData();
			//此处添加两个类型："REMOTE TABLE", "MEMORY TABLE" - clickhouse支持
			String schemaPattern = "%" + dbSchema(datasource) + "%",
					tableNamePattern = StrUtil.isNotBlank(tableName) ? ("%" + tableName + "%") : null;
			ResultSet rs = metaData.getTables(null, schemaPattern, tableNamePattern, new String[] {"TABLE", "VIEW", "REMOTE TABLE", "MEMORY TABLE"});
			while(rs.next()) {
				list.add(new TableViewDto(rs.getString("TABLE_NAME"), rs.getString("REMARKS"), datasource.getId()));
			}
			rs.close();
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		return list;
	}
	
	@Override
	public void executeSql(List<String> sqls, DatasourceDto datasource) throws Exception {
		Connection connection = null;
        Statement statement = null;
        try {
            connection = DatasourceUtil.createConnection(datasource);
            statement = connection.createStatement();
            connection.setAutoCommit(false);
            for (String sql : sqls) {
                statement.execute(sql);
            }
            connection.commit();
        } catch(Exception e){
            if(connection != null){
				connection.rollback();
			}
            throw e;
        } finally {
			IoUtil.close(statement);
			IoUtil.close(connection);
        }
	}
}
