package com.unis.framework.mybatis.core.handler;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.Ordered;

import javax.validation.constraints.NotNull;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @author duhuajie
 * @create 2024-04-29
 * @description 控制台打印完整 sql 语句，也可用于同步内网，把增删改 SQL 生成文件
 */
@Intercepts({
		@Signature(type = StatementHandler.class, method = "batch", args = { Statement.class }),
		@Signature(type = StatementHandler.class, method = "query", args = { Statement.class , ResultHandler.class}),
		@Signature(type = StatementHandler.class, method = "update", args = { Statement.class }) })
@Slf4j
@ConfigurationProperties(prefix = "delivery.print")
@Setter
public class MybatisSqlPrintInterceptor implements Interceptor, Ordered {
	private Configuration configuration = null;


	/**
	 * sql生成文件 的开关，默认为 false
	 */
	@Value("${delivery.print.enable:false}")
	private Boolean enable = false;

	private List<String> tables = Collections.emptyList();

	private List<String> ignore_tables = Collections.emptyList();

	private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
	public static final DateTimeFormatter DATE_FORMAT_THREAD_LOCAL_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
	public static final DateTimeFormatter DATE_FORMAT_THREAD_LOCAL_DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd");
	/**
	 * @param proceed
	 * @param id
	 * @param sql
	 * @param sct
	 * 		redis-key : print_sql:sct:time(1h):id = sql
	 */
	private void printSql(Object proceed, String id, String sql, SqlCommandType sct) {
//		log.info("阿里update SQL 打印结果：{}", proceed);
//		log.info("阿里ID[HASHCODE]  打印："+ (id + sql).hashCode());
//		log.info("阿里ID  打印："+ id);
//		log.info("阿里SQL["+ sct +"] 打印："+ sql);
//		String hash = (id + sql).hashCode() + RandomStringUtils.randomAlphanumeric(10);
		log.info("sql:{}",sql);
	}

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Object proceed = invocation.proceed();
		if(enable) getSqlConf(invocation, proceed);
		return proceed;
    }



	/**
	 * 	打印SQL
	 * @param invocation
	 * @param proceed
	 */
	private void getSqlConf(Invocation invocation, Object proceed) {
		try {
			Object target = invocation.getTarget();
			StatementHandler statementHandler = (StatementHandler) target;
			BoundSql boundSql = statementHandler.getBoundSql();
			// 去掉黑名单，判断是白名单的表才打印
//			if(!isPrint(boundSql)) return;
			PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(statementHandler);
			MappedStatement mappedStatement = mpSh.mappedStatement();
			String id = mappedStatement.getId();
			if (configuration == null) {
				this.configuration = mappedStatement.getConfiguration();
			}
			// 替换参数格式化Sql语句，去除换行符
			String sql = formatSql(boundSql, configuration).concat(";");

			SqlCommandType sct = mappedStatement.getSqlCommandType();
			printSql(proceed, id, sql, sct);
		} catch (Exception e) {
			log.error("==> 打印sql 日志异常 {}",e);
		}
	}

	private boolean isPrint(BoundSql boundSql) {
		boolean flag = false;
		if (tables.isEmpty()) return flag;
		for (String td : ignore_tables)
			if (boundSql.getSql().toLowerCase().contains(td.toLowerCase()))
				return false;
		for (String td : tables)
			if (boundSql.getSql().toLowerCase().contains(td.toLowerCase()))
				return true;
		return flag;
	}


	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}
 
	@Override
	public void setProperties(Properties properties) {

	}
 
	/**
	 * 获取完整的sql实体的信息
     */
	public String formatSql(BoundSql boundSql, Configuration configuration) {
		String sql = boundSql.getSql();
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		Object parameterObject = boundSql.getParameterObject();
		// 输入sql字符串空判断
		if (StringUtils.isBlank(sql)) {
			return "";
		}
		if (configuration == null) {
			return "";
		}
		TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
		sql = beautifySql(sql);
		// 参考mybatis 源码 DefaultParameterHandler
		if (parameterMappings != null) {
			for (ParameterMapping parameterMapping : parameterMappings) {
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (parameterObject == null) {
						value = null;
					} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else {
						MetaObject metaObject = configuration.newMetaObject(parameterObject);
						value = metaObject.getValue(propertyName);
					}
					String paramValueStr = getString(value);
					sql = sql.replaceFirst("\\?", makeQueryStringAllRegExp(paramValueStr));
				}
			}
		}
		return sql;
	}

	private static String getString(Object value) {
		String paramValueStr;
		if (value instanceof String)  {
			paramValueStr = "'" + value + "'";
		} else if(value instanceof Boolean){
			paramValueStr = Boolean.TRUE.equals(value) ? "1": "0";
		} else if (value instanceof Date ) {
			paramValueStr = "'" + DATE_FORMAT_THREAD_LOCAL.get().format(value) + "'";
		} else if (value instanceof LocalDateTime) {
			paramValueStr = "'" + ((LocalDateTime) value).format(DATE_FORMAT_THREAD_LOCAL_DATE_TIME) + "'";
		} else if(value instanceof LocalDate){
			paramValueStr = "'" + ((LocalDate) value).format(DATE_FORMAT_THREAD_LOCAL_DATE) + "'";
		} else if (value instanceof Map) {
			paramValueStr = "'" + JSON.toJSONString(value) + "'";
		} else {
			paramValueStr = value + "";
		}
		return paramValueStr;
	}


	/**
	 * 转义正则特殊字符 （$()*+.[]?\^{}
	 * \\需要第一个替换，否则replace方法替换时会有逻辑bug
	 */
	private static String makeQueryStringAllRegExp(String str) {
		if (str != null && !str.isEmpty()) {
			return getString(str);
		}
		return str;
	}

	@NotNull
	static String getString(String str) {
		return str.replace("\\", "\\\\")
				.replace("*", "\\*")
				.replace("+", "\\+")
				.replace("|", "\\|")
				.replace("{", "\\{")
				.replace("}", "\\}")
				.replace("(", "\\(")
				.replace(")", "\\)")
				.replace("^", "\\^")
				.replace("$", "\\$")
				.replace("[", "\\[")
				.replace("]", "\\]")
				.replace("?", "\\?")
				.replace(",", "\\,")
				.replace(".", "\\.")
				.replace("&", "\\&");
	}

	private String beautifySql(String sql) {
		sql = sql.replaceAll("[\\s\n ]+", " ");
		return sql;
	}
 
	@Override
	public int getOrder() {
		return Ordered.HIGHEST_PRECEDENCE;
	}
 
}
