package cn.aqroid.framework.interceptor;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

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.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import cn.aqroid.common.annotation.TenantIgnore;
import cn.aqroid.common.core.tenant.TenantContext;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;

/**
 * 多租户SQL拦截器
 * 
 * 功能：
 * 1. SELECT: 自动添加 WHERE tenant_id = ?
 * 2. INSERT: 自动添加 tenant_id 字段和值
 * 3. UPDATE: 自动添加 WHERE tenant_id = ?
 * 4. DELETE: 自动添加 WHERE tenant_id = ?
 *
 * @author aqroid
 */
@Component
@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class TenantSqlInterceptor implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(TenantSqlInterceptor.class);

    /**
     * 租户字段名
     */
    private static final String TENANT_ID_COLUMN = "tenant_id";

    /**
     * 忽略租户过滤的表（系统表、字典表等）
     */
    private static final List<String> IGNORE_TABLES = Arrays.asList(
        "sys_tenant",           // 租户表本身
        "sys_tenant_package",   // 租户套餐表
        "sys_config",           // 系统配置
        "sys_dict_type",        // 字典类型
        "sys_dict_data",        // 字典数据
        "sys_menu",             // 菜单表
        "sys_notice",           // 通知公告
        "sys_logininfor",       // 登录日志
        "sys_oper_log",         // 操作日志
        "sys_job",              // 定时任务
        "sys_job_log",          // 任务日志
        "gen_table",            // 代码生成表
        "gen_table_column",     // 代码生成字段
        "customer"              // 顾客表（跨租户）
    );

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        // 获取MappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        // 检查是否忽略租户
        if (shouldIgnoreTenant(mappedStatement)) {
            return invocation.proceed();
        }

        // 获取当前租户ID
        Long tenantId = TenantContext.getTenantId();
        if (tenantId == null) {
            log.debug("未设置租户ID，跳过租户过滤: {}", mappedStatement.getId());
            return invocation.proceed();
        }

        // 获取SQL类型
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

        // 获取BoundSql
        BoundSql boundSql = statementHandler.getBoundSql();
        String originalSql = boundSql.getSql();

        log.debug("原始SQL: {}", originalSql);

        // 解析并改写SQL
        String newSql = rewriteSql(originalSql, sqlCommandType, tenantId);

        // 通过反射修改SQL
        metaObject.setValue("delegate.boundSql.sql", newSql);

        log.debug("改写后SQL: {}", newSql);

        return invocation.proceed();
    }

    /**
     * 判断是否忽略租户
     */
    private boolean shouldIgnoreTenant(MappedStatement mappedStatement) {
        // 1. 检查上下文是否设置了忽略标志
        if (TenantContext.isIgnore()) {
            return true;
        }

        // 2. 检查方法上是否有@TenantIgnore注解
        try {
            String mappedStatementId = mappedStatement.getId();
            String className = mappedStatementId.substring(0, mappedStatementId.lastIndexOf('.'));
            String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf('.') + 1);

            Class<?> clazz = Class.forName(className);
            
            // 检查类级别注解
            TenantIgnore classAnnotation = clazz.getAnnotation(TenantIgnore.class);
            if (classAnnotation != null && classAnnotation.value()) {
                return true;
            }
            
            // 检查方法级别注解
            for (Method method : clazz.getDeclaredMethods()) {
                if (method.getName().equals(methodName)) {
                    TenantIgnore methodAnnotation = method.getAnnotation(TenantIgnore.class);
                    if (methodAnnotation != null && methodAnnotation.value()) {
                        return true;
                    }
                }
            }
        } catch (Exception e) {
            // 忽略异常
        }

        return false;
    }

    /**
     * 改写SQL
     */
    private String rewriteSql(String originalSql, SqlCommandType sqlCommandType, Long tenantId) {
        try {
            Statement statement = CCJSqlParserUtil.parse(originalSql);

            switch (sqlCommandType) {
                case SELECT:
                    processSelect((Select) statement, tenantId);
                    break;
                case INSERT:
                    processInsert((Insert) statement, tenantId);
                    break;
                case UPDATE:
                    processUpdate((Update) statement, tenantId);
                    break;
                case DELETE:
                    processDelete((Delete) statement, tenantId);
                    break;
                default:
                    break;
            }

            return statement.toString();
        } catch (Exception e) {
            log.warn("SQL解析失败，返回原始SQL: {}", e.getMessage());
            return originalSql;
        }
    }

    /**
     * 处理SELECT语句
     */
    private void processSelect(Select select, Long tenantId) {
        if (select.getSelectBody() instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) select.getSelectBody();

            // 检查是否是忽略的表
            if (isIgnoreTable(plainSelect.getFromItem())) {
                return;
            }

            // 添加WHERE tenant_id = ?
            Expression tenantCondition = buildTenantCondition(tenantId);
            Expression where = plainSelect.getWhere();

            if (where != null) {
                plainSelect.setWhere(new AndExpression(tenantCondition, where));
            } else {
                plainSelect.setWhere(tenantCondition);
            }
        }
    }

    /**
     * 处理INSERT语句
     */
    private void processInsert(Insert insert, Long tenantId) {
        // 检查是否是忽略的表
        if (isIgnoreTable(insert.getTable().getName())) {
            return;
        }

        // 添加tenant_id字段
        List<Column> columns = insert.getColumns();
        if (columns != null && !containsTenantColumn(columns)) {
            columns.add(new Column(TENANT_ID_COLUMN));

            // 添加值
            if (insert.getItemsList() instanceof ExpressionList) {
                ((ExpressionList) insert.getItemsList()).getExpressions().add(new LongValue(tenantId));
            }
        }
    }

    /**
     * 处理UPDATE语句
     */
    private void processUpdate(Update update, Long tenantId) {
        // 检查是否是忽略的表
        if (isIgnoreTable(update.getTable().getName())) {
            return;
        }

        // 添加WHERE tenant_id = ?
        Expression tenantCondition = buildTenantCondition(tenantId);
        Expression where = update.getWhere();

        if (where != null) {
            update.setWhere(new AndExpression(tenantCondition, where));
        } else {
            update.setWhere(tenantCondition);
        }
    }

    /**
     * 处理DELETE语句
     */
    private void processDelete(Delete delete, Long tenantId) {
        // 检查是否是忽略的表
        if (isIgnoreTable(delete.getTable().getName())) {
            return;
        }

        // 添加WHERE tenant_id = ?
        Expression tenantCondition = buildTenantCondition(tenantId);
        Expression where = delete.getWhere();

        if (where != null) {
            delete.setWhere(new AndExpression(tenantCondition, where));
        } else {
            delete.setWhere(tenantCondition);
        }
    }

    /**
     * 构建租户条件: tenant_id = ?
     */
    private Expression buildTenantCondition(Long tenantId) {
        EqualsTo equalsTo = new EqualsTo();
        equalsTo.setLeftExpression(new Column(TENANT_ID_COLUMN));
        equalsTo.setRightExpression(new LongValue(tenantId));
        return equalsTo;
    }

    /**
     * 检查是否包含tenant_id字段
     */
    private boolean containsTenantColumn(List<Column> columns) {
        return columns.stream()
            .anyMatch(column -> TENANT_ID_COLUMN.equalsIgnoreCase(column.getColumnName()));
    }

    /**
     * 检查是否是忽略的表
     */
    private boolean isIgnoreTable(Object table) {
        if (table == null) {
            return false;
        }

        String tableName;
        if (table instanceof FromItem) {
            tableName = table.toString();
        } else {
            tableName = table.toString();
        }

        // 移除反引号和别名
        tableName = tableName.replace("`", "").trim();
        // 处理别名情况，如 "sys_tenant t"
        if (tableName.contains(" ")) {
            tableName = tableName.split(" ")[0];
        }

        final String finalTableName = tableName;
        return IGNORE_TABLES.stream()
            .anyMatch(ignoreTable -> ignoreTable.equalsIgnoreCase(finalTableName));
    }

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

    @Override
    public void setProperties(Properties properties) {
        // 可以从配置文件读取参数
    }
}
