package com.artfess.base.interceptor;


import com.artfess.base.constants.SQLConst;
import com.artfess.base.util.BeanUtils;
import com.artfess.base.util.SQLUtil;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.apache.ibatis.executor.resultset.DefaultResultSetHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.checkerframework.checker.units.qual.C;
import org.springframework.util.Assert;
import org.apache.ibatis.mapping.ResultMap;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * MyBatis的返回结果拦截器
 *
 * @company 阿特菲斯信息技术有限公司
 * @author heyifan
 * @email heyf@jee-soft.cn
 * @date 2020年7月27日
 */
@Intercepts({@Signature(type=ResultSetHandler.class,method="handleResultSets",args={Statement.class})})
public class ResultSetInterceptor implements Interceptor{
	private static CCJSqlParserManager sqlParserManager = new CCJSqlParserManager();
	private static TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();

	private List<ResultSetFilter> filters = new ArrayList<>();

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		try {
			Object target = invocation.getTarget();
			MappedStatement ms = getByPropertyName(target, "mappedStatement");
			BoundSql boundSql = getByPropertyName(target, "boundSql");

			DefaultResultSetHandler defaultResultSetHandler=(DefaultResultSetHandler) invocation.getTarget();
			MetaObject metaStatementHandler= SystemMetaObject.forObject(defaultResultSetHandler);
			MappedStatement mappedStatement=(MappedStatement) metaStatementHandler.getValue("mappedStatement");
			//获取节点属性的集合
			List<ResultMap> resultMaps= mappedStatement.getResultMaps();
			//上面一堆代码主要是为了获取sql的映射类型
			Class<?> resultType= resultMaps.get(0).getType();
			//InterceptorFlagInterface这个是我自己定义的一个标记接口，这句代码意思是，如果sql的结果映射类型为map或者实体实现了InterceptorFlagInterface接口的都会走这个if语句


			// MappedStatement ID
			String id = ms.getId();
			String sql = boundSql.getSql();
			// sql中的表名
			List<String> tableNames = getTableNames(sql);
			// 对过滤链进行排序
			// Collections.sort(this.filters, Comparator.comparing(ResultSetFilter::getOrder));

			String dbType = SQLUtil.getDbType();
			// 达梦数据库下返回结果集为Map类型
			if (SQLConst.DB_DM.equals(dbType)&&Map.class.isAssignableFrom(resultType) ) {
				//处理梦数据库下返回结果集为Map类型 其字段名全大写情况
				return MapIntercept.interceptMap(invocation);
			}else {
				Object result = invocation.proceed();
				this.filters.forEach(f -> {
					// 判断当前过滤器是否支持该sql
					if(f.support(id, sql, tableNames)) {
						// 对结果集进行处理
						f.handle(result);
					}
				});
				return result;
			}
			// 遍历所有过滤链


		}
		catch(Exception e) {
			e.printStackTrace();
		}
		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		if(target instanceof ResultSetHandler) {
			if(BeanUtils.isEmpty(this.filters)) {
				return target;
			}
			return Plugin.wrap(target, this);
		}
		return target;
	}

	/**
	 * 通过对象中指定属性名的属性对象
	 * @param <C>
	 * @param obj
	 * @param name
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	public <C> C getByPropertyName(Object obj, String name) throws Exception {
		Class<?> c = obj.getClass();
		Field f = c.getDeclaredField(name);
		f.setAccessible(true);
		return (C)f.get(obj);
	}

	/**
	 * 从SQL语句中提取表名
	 * @param sql
	 * @return
	 * @throws Exception
	 */
    public List<String> getTableNames(String sql) throws Exception {
		net.sf.jsqlparser.statement.Statement statement = sqlParserManager.parse(new StringReader(sql));
		return tablesNamesFinder.getTableList(statement);
	}

	public void setFilters(List<ResultSetFilter> filters) {
		Assert.notNull(filters, "filters can not be empty.");
		this.filters = filters;
	}

	/**
	 * 添加过滤器
	 * @param filter
	 */
	public void addFilters(ResultSetFilter filter) {
		Assert.notNull(filter, "filters can not be empty.");
		this.filters.add(filter);
	}
}
