package com.ruoyi.framework.interceptor;

import java.sql.Connection;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.DepartmentDataConfig;
import com.ruoyi.framework.security.context.DepartmentContextHolder;
import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.service.ISysOperLogService;

/**
 * 部门数据隔离拦截器
 * 
 * 自动为涉及部门隔离的表添加部门过滤条件，确保用户只能访问自己部门的数据。
 * 超级管理员不受此限制，可以访问所有部门的数据。
 * 
 * @author ruoyi
 */
@Component
@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class DepartmentDataInterceptor implements Interceptor
{
    private static final Logger log = LoggerFactory.getLogger(DepartmentDataInterceptor.class);

    @Autowired
    private DepartmentDataConfig config;

    /**
     * 需要进行部门隔离的表名集合
     */
    private static final Set<String> TARGET_TABLES = new HashSet<>(Arrays.asList(
        "dc_contract",
        "dc_service_period", 
        "dc_employee_info",
        "dc_employee_library",
        "dc_credit",
        "dc_bank_institution",
        "dc_financing"
    ));

    /**
     * SQL注入检测模式
     */
    private static final Pattern SQL_INJECTION_PATTERN = Pattern.compile(
        "('.+(\\s)*(or|and)(\\s)+.+=')|('.+(\\s)*(or|and)(\\s)+.+\\s*=\\s*.+)",
        Pattern.CASE_INSENSITIVE
    );

    @Override
    public Object intercept(Invocation invocation) throws Throwable
    {
        try
        {
            // 检查功能是否启用
            if (!config.isEnabled())
            {
                if (config.isSqlLogging())
                {
                    log.debug("部门数据隔离功能已禁用，跳过拦截");
                }
                return invocation.proceed();
            }

            // 超级管理员跳过部门隔离
            if (DepartmentContextHolder.isSuperAdmin())
            {
                if (config.isSqlLogging())
                {
                    log.debug("超级管理员访问，跳过部门数据隔离");
                }
                
                // 记录超级管理员操作日志
                recordSuperAdminOperation(invocation);
                
                return invocation.proceed();
            }

            // 获取当前用户部门ID
            Long currentDeptId = DepartmentContextHolder.getCurrentDeptId();
            if (currentDeptId == null)
            {
                if (config.isSqlLogging())
                {
                    log.warn("无法获取当前用户部门ID，跳过部门数据隔离");
                }
                return invocation.proceed();
            }

            Object target = invocation.getTarget();
            Object[] args = invocation.getArgs();

            if (target instanceof Executor)
            {
                return handleExecutorIntercept(invocation, args, currentDeptId);
            }
            else if (target instanceof StatementHandler)
            {
                return handleStatementHandlerIntercept(invocation, args, currentDeptId);
            }

            return invocation.proceed();
        }
        catch (Exception e)
        {
            log.error("部门数据隔离拦截器执行异常", e);
            // 发生异常时继续执行原始操作，避免影响业务
            return invocation.proceed();
        }
    }

    /**
     * 处理Executor拦截
     */
    private Object handleExecutorIntercept(Invocation invocation, Object[] args, Long currentDeptId) throws Throwable
    {
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object parameter = args[1];

        // 检查是否需要添加部门过滤
        if (needDepartmentFilter(mappedStatement))
        {
            if (config.isSqlLogging())
            {
                log.debug("为SQL操作添加部门过滤条件，部门ID: {}", currentDeptId);
            }
            addDepartmentFilter(parameter, currentDeptId);
        }

        return invocation.proceed();
    }

    /**
     * 处理StatementHandler拦截
     */
    private Object handleStatementHandlerIntercept(Invocation invocation, Object[] args, Long currentDeptId) throws Throwable
    {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String originalSql = boundSql.getSql();

        // 检查SQL是否涉及目标表
        if (containsTargetTables(originalSql))
        {
            String modifiedSql = addDepartmentConditionToSql(originalSql, currentDeptId);
            if (!modifiedSql.equals(originalSql))
            {
                if (config.isSqlLogging())
                {
                    log.debug("修改SQL添加部门过滤条件: {}", modifiedSql);
                }
                // 通过反射修改BoundSql中的SQL
                java.lang.reflect.Field sqlField = BoundSql.class.getDeclaredField("sql");
                sqlField.setAccessible(true);
                sqlField.set(boundSql, modifiedSql);
            }
        }

        return invocation.proceed();
    }

    /**
     * 检查是否需要添加部门过滤
     */
    private boolean needDepartmentFilter(MappedStatement mappedStatement)
    {
        // 获取SQL命令类型
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        
        // 只对SELECT、UPDATE、DELETE操作进行过滤
        if (sqlCommandType != SqlCommandType.SELECT && 
            sqlCommandType != SqlCommandType.UPDATE && 
            sqlCommandType != SqlCommandType.DELETE)
        {
            return false;
        }

        // 检查SQL是否涉及目标表
        BoundSql boundSql = mappedStatement.getBoundSql(null);
        if (boundSql == null)
        {
            return false;
        }

        String sql = boundSql.getSql();
        return containsTargetTables(sql);
    }

    /**
     * 检查SQL是否包含目标表
     */
    private boolean containsTargetTables(String sql)
    {
        if (sql == null || sql.trim().isEmpty())
        {
            return false;
        }

        String lowerSql = sql.toLowerCase();
        for (String table : TARGET_TABLES)
        {
            // 使用正则表达式匹配表名，确保是完整的表名而不是子字符串
            Pattern pattern = Pattern.compile("\\b" + table + "\\b", Pattern.CASE_INSENSITIVE);
            if (pattern.matcher(lowerSql).find())
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 为参数添加部门过滤条件
     */
    private void addDepartmentFilter(Object parameter, Long currentDeptId)
    {
        if (parameter instanceof Map)
        {
            @SuppressWarnings("unchecked")
            Map<String, Object> paramMap = (Map<String, Object>) parameter;
            
            // 如果参数中还没有deptId，则添加
            if (!paramMap.containsKey("deptId"))
            {
                paramMap.put("deptId", currentDeptId);
                if (config.isSqlLogging())
                {
                    log.debug("添加部门过滤参数: deptId = {}", currentDeptId);
                }
            }
        }
    }

    /**
     * 为SQL添加部门条件
     */
    private String addDepartmentConditionToSql(String originalSql, Long currentDeptId)
    {
        if (originalSql == null || originalSql.trim().isEmpty())
        {
            return originalSql;
        }

        // 防止SQL注入
        if (SQL_INJECTION_PATTERN.matcher(String.valueOf(currentDeptId)).find())
        {
            log.warn("检测到潜在的SQL注入风险，跳过部门条件添加");
            return originalSql;
        }

        String sql = originalSql.trim();
        String lowerSql = sql.toLowerCase();

        try
        {
            // 处理SELECT语句
            if (lowerSql.startsWith("select"))
            {
                return addDepartmentConditionToSelect(sql, currentDeptId);
            }
            // 处理UPDATE语句
            else if (lowerSql.startsWith("update"))
            {
                return addDepartmentConditionToUpdate(sql, currentDeptId);
            }
            // 处理DELETE语句
            else if (lowerSql.startsWith("delete"))
            {
                return addDepartmentConditionToDelete(sql, currentDeptId);
            }
        }
        catch (Exception e)
        {
            log.warn("添加部门条件时发生异常，使用原始SQL: {}", e.getMessage());
            return originalSql;
        }

        return originalSql;
    }

    /**
     * 为SELECT语句添加部门条件
     */
    private String addDepartmentConditionToSelect(String sql, Long currentDeptId)
    {
        String lowerSql = sql.toLowerCase();
        
        // 查找WHERE子句的位置
        int whereIndex = lowerSql.indexOf(" where ");
        
        if (whereIndex != -1)
        {
            // 已有WHERE子句，添加AND条件
            StringBuilder sb = new StringBuilder(sql);
            int insertIndex = whereIndex + 7; // " where ".length()
            
            // 为每个目标表添加部门条件
            String tableAlias = findTableAlias(lowerSql, TARGET_TABLES);
            if (tableAlias != null)
            {
                String condition = tableAlias + ".dept_id = " + currentDeptId + " AND ";
                sb.insert(insertIndex, condition);
                
                if (config.isSqlLogging())
                {
                    log.debug("添加部门过滤条件到现有WHERE子句: {}", condition);
                }
            }
            
            return sb.toString();
        }
        else
        {
            // 没有WHERE子句，添加WHERE条件
            // 查找ORDER BY, GROUP BY, HAVING, LIMIT等子句的位置
            String[] keywords = {" order by ", " group by ", " having ", " limit ", " offset "};
            int insertIndex = sql.length();
            
            for (String keyword : keywords)
            {
                int keywordIndex = lowerSql.indexOf(keyword);
                if (keywordIndex != -1 && keywordIndex < insertIndex)
                {
                    insertIndex = keywordIndex;
                }
            }
            
            // 为第一个找到的目标表添加WHERE条件
            String tableAlias = findTableAlias(lowerSql, TARGET_TABLES);
            if (tableAlias != null)
            {
                String whereClause = " WHERE " + tableAlias + ".dept_id = " + currentDeptId;
                String modifiedSql = sql.substring(0, insertIndex) + whereClause + sql.substring(insertIndex);
                
                if (config.isSqlLogging())
                {
                    log.debug("添加新的WHERE子句: {}", whereClause);
                    log.debug("修改后的SQL: {}", modifiedSql);
                }
                
                return modifiedSql;
            }
        }
        
        if (config.isSqlLogging())
        {
            log.warn("无法为SQL添加部门过滤条件，未找到目标表: {}", sql);
        }
        
        return sql;
    }

    /**
     * 查找表的别名
     * 
     * @param lowerSql 小写的SQL语句
     * @param targetTables 目标表集合
     * @return 表别名，如果没有找到则返回null
     */
    private String findTableAlias(String lowerSql, Set<String> targetTables)
    {
        for (String table : targetTables)
        {
            if (lowerSql.contains(table))
            {
                // 对于UPDATE语句，表名在UPDATE关键字之后
                if (lowerSql.trim().startsWith("update"))
                {
                    // 匹配模式：UPDATE table_name [AS] alias SET 或 UPDATE table_name SET
                    String updatePattern = "update\\s+" + table + "\\s+(as\\s+)?(\\w+)\\s+set";
                    java.util.regex.Pattern p = java.util.regex.Pattern.compile(updatePattern, java.util.regex.Pattern.CASE_INSENSITIVE);
                    java.util.regex.Matcher m = p.matcher(lowerSql);
                    
                    if (m.find())
                    {
                        String alias = m.group(2);
                        // 确保别名不是SQL关键字
                        if (alias != null && !isReservedKeyword(alias))
                        {
                            return alias;
                        }
                    }
                    
                    // 如果没有找到别名，直接使用表名
                    return table;
                }
                else
                {
                    // 对于SELECT语句，查找表名后的别名
                    // 匹配模式：table_name alias 或 table_name as alias
                    String pattern = "\\b" + table + "\\s+(as\\s+)?(\\w+)";
                    java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern, java.util.regex.Pattern.CASE_INSENSITIVE);
                    java.util.regex.Matcher m = p.matcher(lowerSql);
                    
                    if (m.find())
                    {
                        String alias = m.group(2);
                        // 确保别名不是SQL关键字
                        if (!isReservedKeyword(alias))
                        {
                            return alias;
                        }
                    }
                    
                    // 如果没有找到别名，使用表名本身
                    return table;
                }
            }
        }
        return null;
    }

    /**
     * 检查是否为SQL保留关键字
     */
    private boolean isReservedKeyword(String word)
    {
        String[] keywords = {"inner", "left", "right", "join", "on", "where", "and", "or", "order", "by", "group", "having", "limit", "offset", "set", "from", "into", "values"};
        for (String keyword : keywords)
        {
            if (keyword.equalsIgnoreCase(word))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 为UPDATE语句添加部门条件
     */
    private String addDepartmentConditionToUpdate(String sql, Long currentDeptId)
    {
        String lowerSql = sql.toLowerCase();
        int whereIndex = lowerSql.indexOf(" where ");
        
        if (whereIndex != -1)
        {
            // 已有WHERE子句，添加AND条件
            String tableAlias = findTableAlias(lowerSql, TARGET_TABLES);
            if (tableAlias != null)
            {
                String condition = " AND " + tableAlias + ".dept_id = " + currentDeptId;
                return sql + condition;
            }
        }
        else
        {
            // 没有WHERE子句，添加WHERE条件
            String tableAlias = findTableAlias(lowerSql, TARGET_TABLES);
            if (tableAlias != null)
            {
                String whereClause = " WHERE " + tableAlias + ".dept_id = " + currentDeptId;
                return sql + whereClause;
            }
        }
        
        return sql;
    }

    /**
     * 为DELETE语句添加部门条件
     */
    private String addDepartmentConditionToDelete(String sql, Long currentDeptId)
    {
        // DELETE语句的处理逻辑与UPDATE类似
        return addDepartmentConditionToUpdate(sql, currentDeptId);
    }

    @Override
    public Object plugin(Object target)
    {
        if (target instanceof Executor || target instanceof StatementHandler)
        {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties)
    {
        // 可以通过properties配置拦截器参数
        log.info("部门数据隔离拦截器初始化完成");
    }

    /**
     * 记录超级管理员操作日志
     * 
     * @param invocation MyBatis拦截器调用对象
     */
    private void recordSuperAdminOperation(Invocation invocation)
    {
        try
        {
            Object target = invocation.getTarget();
            Object[] args = invocation.getArgs();
            
            // 只记录涉及目标表的操作
            if (target instanceof Executor && args.length > 0)
            {
                MappedStatement mappedStatement = (MappedStatement) args[0];
                
                // 检查是否涉及目标表
                if (needDepartmentFilter(mappedStatement))
                {
                    String operationType = getOperationType(mappedStatement.getSqlCommandType());
                    String tableName = getTargetTableName(mappedStatement);
                    
                    // 创建操作日志
                    SysOperLog operLog = new SysOperLog();
                    operLog.setTitle("部门数据隔离-超级管理员操作");
                    operLog.setBusinessType(getBusinessType(mappedStatement.getSqlCommandType()));
                    operLog.setMethod(mappedStatement.getId());
                    operLog.setRequestMethod("DATABASE");
                    operLog.setOperatorType(1); // 后台用户
                    operLog.setOperName(DepartmentContextHolder.getCurrentUsername());
                    operLog.setDeptName("超级管理员");
                    operLog.setOperUrl("部门数据访问");
                    
                    // 获取IP地址
                    try
                    {
                        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
                        operLog.setOperIp(ipAddr);
                    }
                    catch (Exception e)
                    {
                        operLog.setOperIp("127.0.0.1");
                    }
                    
                    // 设置操作参数
                    String operParam = String.format("操作类型: %s, 目标表: %s, 用户: %s", 
                        operationType, tableName, DepartmentContextHolder.getCurrentUsername());
                    operLog.setOperParam(operParam);
                    
                    operLog.setJsonResult("超级管理员跨部门数据访问");
                    operLog.setStatus(0); // 正常
                    operLog.setOperTime(new Date());
                    
                    // 异步记录日志，避免影响业务性能
                    try
                    {
                        ISysOperLogService operLogService = SpringUtils.getBean(ISysOperLogService.class);
                        operLogService.insertOperlog(operLog);
                        
                        if (config.isSqlLogging())
                        {
                            log.info("记录超级管理员操作日志: 用户={}, 操作={}, 表={}", 
                                DepartmentContextHolder.getCurrentUsername(), operationType, tableName);
                        }
                    }
                    catch (Exception e)
                    {
                        log.warn("记录超级管理员操作日志失败: {}", e.getMessage());
                    }
                }
            }
        }
        catch (Exception e)
        {
            log.warn("记录超级管理员操作日志时发生异常: {}", e.getMessage());
        }
    }

    /**
     * 获取操作类型描述
     * 
     * @param sqlCommandType SQL命令类型
     * @return 操作类型描述
     */
    private String getOperationType(SqlCommandType sqlCommandType)
    {
        switch (sqlCommandType)
        {
            case SELECT:
                return "查询";
            case INSERT:
                return "新增";
            case UPDATE:
                return "修改";
            case DELETE:
                return "删除";
            default:
                return "其他";
        }
    }

    /**
     * 获取业务类型编码
     * 
     * @param sqlCommandType SQL命令类型
     * @return 业务类型编码
     */
    private Integer getBusinessType(SqlCommandType sqlCommandType)
    {
        switch (sqlCommandType)
        {
            case SELECT:
                return 0; // 其它
            case INSERT:
                return 1; // 新增
            case UPDATE:
                return 2; // 修改
            case DELETE:
                return 3; // 删除
            default:
                return 0; // 其它
        }
    }

    /**
     * 获取目标表名
     * 
     * @param mappedStatement MyBatis映射语句
     * @return 目标表名
     */
    private String getTargetTableName(MappedStatement mappedStatement)
    {
        try
        {
            BoundSql boundSql = mappedStatement.getBoundSql(null);
            if (boundSql != null)
            {
                String sql = boundSql.getSql().toLowerCase();
                for (String table : TARGET_TABLES)
                {
                    if (sql.contains(table))
                    {
                        return table;
                    }
                }
            }
        }
        catch (Exception e)
        {
            log.debug("获取目标表名失败: {}", e.getMessage());
        }
        return "未知表";
    }
}