package com.tuoheng.admin.data;

import cn.hutool.core.util.ClassUtil;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.tuoheng.common.core.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
//@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DataPermissionInterceptor implements Interceptor {

    //扫描的包路径（根据自己的项目路径来），这里是取的配置里的包路径
//    @Value("${permission.package-path}")
//    private String packagePath;

    private String packagePath = "com.tuoheng.admin.mapper";

    private final static String DEPT_ID = "dept_id";

    private final static String USER_ID = "create_user";

    private static List<String> classNames;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {

            List<String> deptIds = new ArrayList<>();
            deptIds.add("1");
            deptIds.add("2");
            deptIds.add("3");

            //反射扫包会比较慢，这里做了个懒加载
            if (classNames == null) {
                synchronized (LazyInit.class) {
                    if (classNames == null) {
                        //扫描指定包路径下所有包含指定注解的类
                        Set<Class<?>> classSet = ClassUtil.scanPackageByAnnotation(packagePath, DataPermission.class);
                        if (classSet == null && classSet.size() == 0) {
                            classNames = new ArrayList<>();
                        } else {
                            //取得类全名
                            classNames = classSet.stream().map(Class::getName).collect(Collectors.toList());
                        }
                    }
                }
            }

            // 拿到mybatis的一些对象
            StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
            MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

            // mappedStatement.getId()为执行的mapper方法的全路径名,newId为执行的mapper方法的类全名
            String newId = mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf("."));

            // 如果不是指定的方法，直接结束拦截
            if (!classNames.contains(newId)) {
                return invocation.proceed();
            }
            String newName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1, mappedStatement.getId().length());

            //是否开启数据权限
            boolean isPermi = true;
            Class<?> clazz = Class.forName(newId);

            //遍历方法
            for (Method method : clazz.getDeclaredMethods()) {
                //方法是否含有DataPermission注解，如果含有注解则将数据结果过滤
                if (method.isAnnotationPresent(DataPermission.class) && newName.equals(method.getName())) {
                    DataPermission dataPermission = method.getAnnotation(DataPermission.class);
                    if (dataPermission != null) {
                        //不验证
                        if (!dataPermission.isPermission()) {
                            isPermi = false;
                        } else {
                            //开启验证
                            isPermi = true;
                        }
                    }
                }
            }

            if (isPermi) {
                // 获取到原始sql语句
                String sql = statementHandler.getBoundSql().getSql();

                // 解析并返回新的SQL语句，只处理查询sql
                if (mappedStatement.getSqlCommandType().toString().equals("SELECT")) {
                    //                    String newSql = getNewSql(sql,deptIds,user.getUserId());
                    sql = getSql(sql, deptIds, 1L);
                }

                // 修改sql
                metaObject.setValue("delegate.boundSql.sql", sql);
            }
            return invocation.proceed();
        } catch (Exception e) {
            log.error("数据权限隔离异常：", e);
            return invocation.proceed();
        }

    }


    /**
     * 解析SQL语句，并返回新的SQL语句
     * 注意，该方法使用了JSqlParser来操作SQL，该依赖包Mybatis-plus已经集成了。如果要单独使用，请先自行导入依赖
     *
     * @param sql 原SQL
     * @return 新SQL
     */
    private String getSql(String sql, List<String> deptIds, Long userId) {

        try {
            String condition = "";
            String permissionSql = "(";
            if (deptIds.size() > 0) {
                for (String deptId : deptIds) {
                    if ("(".equals(permissionSql)) {
                        permissionSql = permissionSql + deptId;
                    } else {
                        permissionSql = permissionSql + "," + deptId;
                    }
                }
                permissionSql = permissionSql + ")";

                // 修改原语句
                condition = DEPT_ID + " in " + permissionSql;
            } else {
                condition = USER_ID + " = " + userId;
            }

            if (StringUtils.isBlank(condition)) {
                return sql;
            }

            Select select = (Select) CCJSqlParserUtil.parse(sql);
            PlainSelect plainSelect = (PlainSelect) select.getSelectBody();

            //取得原SQL的where条件
            final Expression expression = plainSelect.getWhere();

            //增加新的where条件
            final Expression envCondition = CCJSqlParserUtil.parseCondExpression(condition);
            if (expression == null) {
                plainSelect.setWhere(envCondition);
            } else {
                AndExpression andExpression = new AndExpression(expression, envCondition);
                plainSelect.setWhere(andExpression);
            }
            return plainSelect.toString();
        } catch (JSQLParserException e) {
            log.error("解析原SQL并构建新SQL错误：" + e);
            return sql;
        }
    }
}