package com.youlai.boot.framework.mybatis.interceptor;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.youlai.boot.common.annotation.DataPermission;
import com.youlai.boot.common.enums.DataScopeEnum;
import com.youlai.boot.framework.security.model.RoleDataScope;
import com.youlai.boot.framework.security.model.SysUserDetails;
import com.youlai.boot.framework.security.util.SecurityUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;

import java.lang.reflect.Method;
import java.util.List;

/**
 * 数据权限控制器
 * <p>
 * 支持多角色数据权限合并（并集策略）：
 * - 如果任一角色是 ALL，则跳过数据权限过滤
 * - 否则用 OR 连接各角色的数据权限条件
 * <p>
 * 使用 JSQLParser 构建 SQL 条件，避免字符串拼接，提高代码安全性和可读性。
 *
 * @author zc
 * @since 2021-12-10 13:28
 */
@Slf4j
public class MyDataPermissionHandler implements DataPermissionHandler {

    private static final String DEPT_TABLE = "sys_dept";
    private static final String DEPT_ID_COLUMN = "id";
    private static final String DEPT_TREE_PATH_COLUMN = "tree_path";

    /**
     * 获取数据权限的sql片段
     *
     * @param where             查询条件
     * @param mappedStatementId mapper接口方法的全路径
     * @return sql片段
     */
    @Override
    @SneakyThrows
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
        // 如果是未登录，或者是定时任务执行的SQL，或者是超级管理员，直接返回
        if (SecurityUtils.getUserId() == null || SecurityUtils.isRoot()) {
            return where;
        }

        // 获取当前用户的数据权限列表
        List<RoleDataScope> dataScopes = SecurityUtils.getUser()
                .map(SysUserDetails::getDataScopes)
                .orElse(List.of());

        // 如果任一角色是 ALL，则跳过数据权限过滤（并集策略）
        if (hasAllDataScope(dataScopes)) {
            return where;
        }

        // 如果没有数据权限，跳过过滤
        if (CollectionUtil.isEmpty(dataScopes)) {
            return where;
        }

        // 获取当前执行的接口类
        Class<?> clazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(StringPool.DOT)));
        // 获取当前执行的方法名称
        String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringPool.DOT) + 1);
        // 获取当前执行的接口类里所有的方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            // 找到当前执行的方法
            if (method.getName().equals(methodName)) {
                DataPermission annotation = method.getAnnotation(DataPermission.class);
                // 判断当前执行的方法是否有权限注解，如果没有注解直接返回
                if (annotation == null) {
                    return where;
                }
                // 使用并集策略过滤
                return dataScopeFilterWithUnion(mappedStatementId, annotation, dataScopes, where);
            }
        }
        return where;
    }

    /**
     * 判断是否包含"全部数据"权限
     *
     * @param dataScopes 数据权限列表
     * @return 是否有全部数据权限
     */
    private boolean hasAllDataScope(List<RoleDataScope> dataScopes) {
        if (CollectionUtil.isEmpty(dataScopes)) {
            return false;
        }
        return dataScopes.stream()
                .anyMatch(scope -> DataScopeEnum.ALL.getValue().equals(scope.getDataScope()));
    }

    /**
     * 使用并集策略进行数据权限过滤
     * <p>
     * 多个角色的数据权限通过 OR 连接，实现并集效果
     *
     * @param annotation  数据权限注解
     * @param dataScopes  数据权限列表
     * @param where       原始查询条件
     * @return 追加权限过滤后的查询条件
     */
    @SneakyThrows
    private Expression dataScopeFilterWithUnion(String mappedStatementId, DataPermission annotation, List<RoleDataScope> dataScopes, Expression where) {
        String deptAlias = annotation.deptAlias();
        String deptIdColumnName = annotation.deptIdColumnName();
        String userAlias = annotation.userAlias();
        String userIdColumnName = annotation.userIdColumnName();

        // 构建各角色的数据权限条件，使用 OR 连接实现并集
        Expression unionExpression = null;
        for (RoleDataScope dataScope : dataScopes) {
            Expression roleExpression = buildRoleDataScopeExpression(
                    deptAlias, deptIdColumnName, userAlias, userIdColumnName, dataScope);
            if (roleExpression != null) {
                if (unionExpression == null) {
                    unionExpression = roleExpression;
                } else {
                    // 使用 OR 连接各角色的条件（并集）
                    unionExpression = new OrExpression(unionExpression, roleExpression);
                }
            }
        }

        if (unionExpression == null) {
            return where;
        }

        // 用括号包裹并集条件
        Expression finalExpression = CCJSqlParserUtil.parseCondExpression("(" + unionExpression + ")");

        if (where == null) {
            log.debug("DataPermission applied. mappedStatementId={}, segment={}", mappedStatementId, finalExpression);
            return finalExpression;
        }

        Expression combined = new AndExpression(where, finalExpression);
        log.debug("DataPermission applied. mappedStatementId={}, originWhere={}, segment={}, combined={}",
                mappedStatementId, where, finalExpression, combined);
        return combined;
    }

    /**
     * 构建单个角色的数据权限SQL条件
     * <p>
     * 使用 JSQLParser 构建 Expression，避免字符串拼接
     *
     * @param deptAlias        部门表别名
     * @param deptIdColumnName 部门ID字段名
     * @param userAlias        用户表别名
     * @param userIdColumnName 用户ID字段名
     * @param roleDataScope    角色数据权限
     * @return 数据权限条件表达式
     */
    private Expression buildRoleDataScopeExpression(String deptAlias, String deptIdColumnName,
                                                     String userAlias, String userIdColumnName,
                                                     RoleDataScope roleDataScope) {
        Column deptColumn = buildColumn(deptAlias, deptIdColumnName);
        Column userColumn = buildColumn(userAlias, userIdColumnName);

        Long deptId = SecurityUtils.getDeptId();
        Long userId = SecurityUtils.getUserId();

        DataScopeEnum dataScopeEnum = DataScopeEnum.getByValue(roleDataScope.getDataScope());
        if (dataScopeEnum == null) {
            return null;
        }

        return switch (dataScopeEnum) {
            case ALL -> null; // 全部数据权限，不添加过滤条件
            case DEPT_AND_SUB -> buildDeptAndSubExpression(deptColumn, deptId);
            case DEPT -> buildEqualsExpression(deptColumn, deptId);
            case SELF -> buildEqualsExpression(userColumn, userId);
            case CUSTOM -> buildCustomDeptExpression(deptColumn, roleDataScope.getCustomDeptIds());
        };
    }

    /**
     * 构建列引用
     *
     * @param alias      表别名
     * @param columnName 列名
     * @return 列引用
     */
    private Column buildColumn(String alias, String columnName) {
        if (StrUtil.isNotBlank(alias)) {
            return new Column(alias + StringPool.DOT + columnName);
        }
        return new Column(columnName);
    }

    /**
     * 构建等于条件
     *
     * @param column 列
     * @param value  值
     * @return 等于表达式
     */
    private Expression buildEqualsExpression(Column column, Long value) {
        EqualsTo equalsTo = new EqualsTo();
        equalsTo.setLeftExpression(column);
        equalsTo.setRightExpression(new LongValue(value));
        return equalsTo;
    }

    /**
     * 构建部门及子部门数据权限条件
     * <p>
     * SQL: dept_id IN (SELECT id FROM sys_dept WHERE id = ? OR FIND_IN_SET(?, tree_path))
     *
     * @param deptColumn 部门列
     * @param deptId     部门ID
     * @return IN 子查询表达式
     */
    @SneakyThrows
    private Expression buildDeptAndSubExpression(Column deptColumn, Long deptId) {
        // 使用字符串解析，避免不同 JSqlParser 版本下 InExpression/ItemsList 渲染差异导致 SQL 语法错误
        // SQL: dept_id IN (SELECT id FROM sys_dept WHERE id = ? OR FIND_IN_SET(?, tree_path))
        String columnName = deptColumn.toString();
        String sql = columnName + " IN (SELECT " + DEPT_ID_COLUMN + " FROM " + DEPT_TABLE +
                " WHERE " + DEPT_ID_COLUMN + " = " + deptId +
                " OR FIND_IN_SET(" + deptId + ", " + DEPT_TREE_PATH_COLUMN + "))";
        return CCJSqlParserUtil.parseCondExpression(sql);
    }

    /**
     * 构建自定义部门数据权限条件
     * <p>
     * SQL: dept_id IN (?, ?, ...)
     *
     * @param deptColumn    部门列
     * @param customDeptIds 自定义部门ID列表
     * @return IN 表达式，如果没有部门则返回 1=0
     */
    @SneakyThrows
    private Expression buildCustomDeptExpression(Column deptColumn, List<Long> customDeptIds) {
        if (CollectionUtil.isEmpty(customDeptIds)) {
            // 没有自定义部门，返回 1=0（无权限）
            EqualsTo falseCondition = new EqualsTo();
            falseCondition.setLeftExpression(new LongValue(1));
            falseCondition.setRightExpression(new LongValue(0));
            return falseCondition;
        }

        // 使用字符串解析，确保渲染始终为 IN (..)
        String columnName = deptColumn.toString();
        String ids = customDeptIds.stream().map(String::valueOf).reduce((a, b) -> a + "," + b).orElse("");
        String sql = columnName + " IN (" + ids + ")";
        return CCJSqlParserUtil.parseCondExpression(sql);
    }

}
