package com.gci.boot.interceptor;

import com.gci.boot.common.util.SpringContextUtil;
import com.gci.boot.constant.DataPermissionConstant;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.util.Properties;

/**
 * mybatis拦截器，自动添加数据权限条件
 *
 */
@Component
@Slf4j
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) })
public class MybatisDataPermissionInterceptor implements Interceptor {

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		//获取请求头中数据权限条件
        if(RequestContextHolder.getRequestAttributes() == null){
            return invocation.proceed();
        }
		HttpServletRequest request = SpringContextUtil.getHttpServletRequest();
        if(request == null){
            return invocation.proceed();
        }
		String dataPermissionCond = (String)request.getAttribute(DataPermissionConstant.DATA_PERMISSION_CONDITION);
		//没有数据权限控制则直接进入下一步
		if(StringUtils.isBlank(dataPermissionCond)){
			return invocation.proceed();
		}

		//获取sql语句信息
		Object[] args = invocation.getArgs();
		MappedStatement mappedStatement = (MappedStatement) args[0];
		String sqlId = mappedStatement.getId();

		//判断是否目标sqlId
		String[] sqlIds = (String[])request.getAttribute(DataPermissionConstant.DATA_PERMISSION_SQL_ID);
		boolean match = false;
		for(String id : sqlIds){
			if(StringUtils.isNotBlank(id) && id.equals(sqlId)){
				match = true;
				break;
			}
		}
		if(!match){
			return invocation.proceed();
		}


		log.debug("------sqlId------" + sqlId);
		SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
		Object parameter = args[1];
		log.debug("------sqlCommandType------" + sqlCommandType);

		RowBounds rowBounds = (RowBounds) args[2];
		ResultHandler resultHandler = (ResultHandler) args[3];
		Executor executor = (Executor) invocation.getTarget();
		CacheKey cacheKey;
		BoundSql boundSql;

		//由于逻辑关系，只会进入一次
		if (args.length == 4) {
			//4 个参数时
			boundSql = mappedStatement.getBoundSql(parameter);
			cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql);
		} else {
			//6 个参数时
			cacheKey = (CacheKey) args[4];
			boundSql = (BoundSql) args[5];
		}

		if (SqlCommandType.SELECT == sqlCommandType) {
			if(StringUtils.isNotBlank(dataPermissionCond)) {
				String sql = appendConditionSql(boundSql.getSql(), dataPermissionCond);
				Field sqlField = BoundSql.class.getDeclaredField("sql");
				sqlField.setAccessible(true);
				sqlField.set(boundSql, sql);
				sqlField.setAccessible(false);
				return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
			}
		}

		return invocation.proceed();
	}

	public static String appendConditionSql(String oldSql, String whereSql) {
		try {
			Select select = (Select) CCJSqlParserUtil.parse(new StringReader(oldSql));
			SelectBody selectBody = select.getSelectBody();

			PlainSelect plainSelect = (PlainSelect) selectBody;
			Expression where = plainSelect.getWhere();
			if (null == where) {
				plainSelect.setWhere(new Column(whereSql));
			} else {
				plainSelect.setWhere(new AndExpression(new Parenthesis(where), new Column("(" + whereSql + ")")));
			}
			return select.toString();
		} catch (JSQLParserException e) {
			e.printStackTrace();
		}
		return oldSql;
	}

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

	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
	}

}
