package com.bonus.material.interceptor;

import com.bonus.common.core.utils.encryption.Sm4Utils;
import com.bonus.material.basic.domain.BmProject;
import com.bonus.system.api.domain.SysDept;
import com.bonus.system.api.domain.SysUser;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * mybatis 拦截器
 * 对用户和部门实体里的邮箱和电话号码进行加密存库，并从库里查询后解密
 * @author weiweiwang
 */
@Intercepts({
//        @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}),
//        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DataEnDecryptInterceptor implements Interceptor {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    final  static String USER_MAPPER_ID = "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN";
    final  static String DEPT_MAPPER_ID = "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNxxxNNN";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            if (invocation.getTarget() instanceof ParameterHandler) {
                // Handle encryption before setting parameters
                ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
                MappedStatement mappedStatement = getMappedStatement(parameterHandler);
                PreparedStatement preparedStatement = (PreparedStatement) invocation.getArgs()[0];
                Object parameterObject = parameterHandler.getParameterObject();
                String sqlId = mappedStatement.getId();
                if (sqlId.contains(USER_MAPPER_ID)){
                    encryptUserObject(parameterObject);
                } else if (sqlId.contains(DEPT_MAPPER_ID)){
                    encryptDeptObject(parameterObject);
                }
                return invocation.proceed();

            } else if (invocation.getTarget() instanceof ResultSetHandler) {
                // Handle decryption after result set is obtained
                ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();

                MappedStatement mappedStatement = getMappedStatement(resultSetHandler);
                Object result = invocation.proceed();

                String sqlId = mappedStatement.getId();
                if (sqlId.contains(USER_MAPPER_ID))
                {
                    decryUserObject(result);
                } else if (sqlId.contains(DEPT_MAPPER_ID)){
                    decryDeptObject(result);
                }
                return result;
            }

            return invocation.proceed();
        }
        catch (Exception e){
            logger.error("mybatis对敏感数据加解密拦截器异常报错", e);
            return invocation.proceed();
        }
    }

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

    @Override
    public void setProperties(Properties properties) {
    }

    private void encryptUserObject(Object parameterObject){
        if (parameterObject == null) {
            logger.warn("encryptUserObject: parameterObject is null");
            return;
        }
        if (parameterObject instanceof BmProject) {
            BmProject pro = (BmProject) parameterObject;
            // 加密敏感字段
            encryptField(pro, "telphone");
        }
    }

    private void encryptDeptObject(Object parameterObject) {
        if (parameterObject == null) {
            logger.warn("encryptDeptObject: parameterObject is null");
            return;
        }
        if (parameterObject instanceof SysDept) {
            SysDept dept = (SysDept) parameterObject;
            // 加密敏感字段
            encryptField(dept, "email");
            encryptField(dept, "phone");
        }
    }

    private void encryptField(Object obj, String fieldName) {
        try {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            Object value = field.get(obj);
            if (value != null && value instanceof String) {
                String encryptedValue = Sm4Utils.encrypt((String) value);
                field.set(obj, encryptedValue);
            }
        } catch (Exception e) {
            logger.error("Failed to encrypt field {} in {}", fieldName, obj.getClass().getName(), e);
        }
    }

    private void decryUserObject(Object result){
        try {
            if (result instanceof ArrayList) {
                List<?> list = (List<?>) result;
                for (Object obj : list) {
                    if (obj instanceof BmProject) {
                        decryptUser ((BmProject) obj);
                    }
                }
            } else if (result instanceof BmProject) {
                decryptUser ((BmProject) result);
            }
        } catch (Exception e) {
            logger.error("Error decrypting user object", e);
        }
    }

    private void decryptUser(BmProject pro) {
        if (pro == null) {
            logger.warn("decryptUser: pro is null");
            return;
        }
        decryptField(pro, "telphone");
    }

    private void decryDeptObject(Object result){
        try {
            if (result instanceof ArrayList) {
                List<?> list = (List<?>) result;
                for (Object obj : list) {
                    if (obj instanceof SysDept) {
                        decryptDept ((SysDept) obj);
                    }
                }
            } else if (result instanceof SysDept) {
                decryptDept ((SysDept) result);
            }
        } catch (Exception e) {
            logger.error("Error decrypting dept object", e);
        }
    }

    private void decryptDept(SysDept dept) {
        if (dept == null) {
            logger.warn("decryptDept: dept is null");
            return;
        }
        decryptField(dept, "email");
        decryptField(dept, "phone");
    }

    private void decryptField(Object obj, String fieldName) {
        try {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            Object value = field.get(obj);
            if (value != null && value instanceof String) {
                String decryptedValue = Sm4Utils.decrypt((String) value);
                field.set(obj, decryptedValue);
            }
        } catch (Exception e) {
            logger.error("Failed to decrypt field {} in {}", fieldName, obj.getClass().getName(), e);
        }
    }

    private MappedStatement getMappedStatement(ParameterHandler parameterHandler) {
        try {
            // Use reflection to access the private field `mappedStatement` (or appropriate field)
            Field mappedStatementField = parameterHandler.getClass().getDeclaredField("mappedStatement");
            mappedStatementField.setAccessible(true);
            return (MappedStatement) mappedStatementField.get(parameterHandler);
        } catch (Exception e) {
            throw new RuntimeException("Failed to get MappedStatement from ParameterHandler", e);
        }
    }


    private MappedStatement getMappedStatement(ResultSetHandler resultSetHandler) {
        try {
            // Use reflection to access the private field `mappedStatement` (or appropriate field)
            Field mappedStatementField = resultSetHandler.getClass().getDeclaredField("mappedStatement");
            mappedStatementField.setAccessible(true);
            return (MappedStatement) mappedStatementField.get(resultSetHandler);
        } catch (Exception e) {
            throw new RuntimeException("Failed to get MappedStatement from ResultSetHandler", e);
        }
    }
}
