package com.bonus.sgzb.common.log.aspect;

import java.util.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.bonus.sgzb.base.api.domain.*;
import com.bonus.sgzb.common.core.constant.SecurityConstants;
import com.bonus.sgzb.common.core.domain.R;
import com.bonus.sgzb.common.log.annotation.Log;
import com.bonus.sgzb.common.log.enums.BusinessType;
import com.bonus.sgzb.common.log.filter.PropertyPreExcludeFilter;
import com.bonus.sgzb.common.log.service.AsyncLogService;
import com.bonus.sgzb.system.api.RemoteUserService;
import com.bonus.sgzb.system.api.domain.BmNumLogs;
import com.bonus.sgzb.system.api.model.LoginUser;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson2.JSON;
import com.bonus.sgzb.common.core.utils.ServletUtils;
import com.bonus.sgzb.common.core.utils.StringUtils;
import com.bonus.sgzb.common.core.utils.ip.IpUtils;
import com.bonus.sgzb.common.log.enums.BusinessStatus;
import com.bonus.sgzb.common.security.utils.SecurityUtils;
import com.bonus.sgzb.system.api.domain.SysOperLog;

/**
 * 操作日志记录处理
 * 
 * @author ruoyi
 */
@Aspect
@Component
public class LogAspect
{
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    /** 排除敏感属性字段 */
    public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };

    /** 计算操作消耗时间 */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    private static ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private AsyncLogService asyncLogService;

    @Autowired
    private RemoteUserService remoteUserService;

    /**
     * 处理请求前执行
     */
    @Before(value = "@annotation(controllerLog)")
    public void boBefore(JoinPoint joinPoint, Log controllerLog)
    {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
    {
        if (controllerLog.businessType() == BusinessType.MATERIAL) {
            handleMaterialLog(joinPoint, controllerLog, null, jsonResult);
        } else {
            handleLog(joinPoint, controllerLog, null, jsonResult);
        }
    }

    /**
     * 拦截异常操作
     * 
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
    {
        handleLog(joinPoint, controllerLog, e, null);
    }

    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
    {
        try
        {
            // *========数据库日志=========*//
            SysOperLog operLog = new SysOperLog();
            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
            // 请求的地址
            String ip = IpUtils.getIpAddr();
            operLog.setOperIp(ip);
            operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
            String username = SecurityUtils.getUsername();
            if (StringUtils.isNotBlank(username))
            {
                operLog.setOperName(username);
            }

            if (e != null)
            {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operLog.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
            // 设置消耗时间
            operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
            // 保存数据库
            asyncLogService.saveSysLog(operLog);
        }
        catch (Exception exp)
        {
            // 记录本地异常日志
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }
        finally
        {
            TIME_THREADLOCAL.remove();
        }
    }

    protected void handleMaterialLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
        try
        {
            List<BmStorageLog> bmStorageLogList = new ArrayList<>();

            // 处理设置注解上的参数
            setRequestValue(joinPoint, bmStorageLogList);

            for (BmStorageLog bmStorageLog : bmStorageLogList) {
                bmStorageLog.setStatus(BusinessStatus.SUCCESS.ordinal());

                // 请求的地址
                bmStorageLog.setMethod(StringUtils.substring(Objects.requireNonNull(ServletUtils.getRequest()).getRequestURI(), 0, 255));
                String username = SecurityUtils.getUsername();
                R<LoginUser> userInfo = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
                if (ObjectUtils.isNotEmpty(userInfo))
                {
                    bmStorageLog.setCreator(userInfo.getData().getSysUser().getNickName());
                }

                if (e != null) {
                    bmStorageLog.setStatus(BusinessStatus.FAIL.ordinal());
                    bmStorageLog.setJsonResult(StringUtils.substring(e.getMessage(), 0, 2000));
                }
                // 设置方法名称
                String className = joinPoint.getTarget().getClass().getName();
                String methodName = joinPoint.getSignature().getName();
                bmStorageLog.setModelTitle(className + "." + methodName + "()");
                // 设置请求方式
                bmStorageLog.setRequestMethod(ServletUtils.getRequest().getMethod());

                bmStorageLog.setModelTitle(controllerLog.title());

                if (StringUtils.isNotNull(jsonResult)) {
                    bmStorageLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
                    HttpResult msgCode = objectMapper.readValue(bmStorageLog.getJsonResult(), HttpResult.class);
                    bmStorageLog.setResultCode(msgCode.getCode());
                    bmStorageLog.setStatus(msgCode.getCode()==200 ? 0 : 1);
                    bmStorageLog.setResultMsg(msgCode.getMsg());
                }
            }

            // 保存数据库
            asyncLogService.batchInsert(bmStorageLogList);
        }
        catch (Exception exp)
        {
            // 记录本地异常日志
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }
        finally
        {
            TIME_THREADLOCAL.remove();
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     * 
     * @param log 日志
     * @param operLog 操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
    {
        // 设置action动作
        operLog.setBusinessType(log.businessType().ordinal());
        // 设置标题
        operLog.setTitle(log.title());
        // 设置操作人类别
        operLog.setOperatorType(log.operatorType().ordinal());
        // 是否需要保存request，参数和值
        if (log.isSaveRequestData())
        {
            // 获取参数的信息，传入到数据库中。
            setRequestValue(joinPoint, operLog, log.excludeParamNames());
        }
        // 是否需要保存response，参数和值
        if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
        {
            operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param bmStorageLogList 物资日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, List<BmStorageLog> bmStorageLogList) throws Exception {
        String requestMethod = ServletUtils.getRequest().getMethod();
        Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
        if (StringUtils.isEmpty(paramsMap) && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) {
            // 盘点入库操作
            if (joinPoint.getArgs()[0] instanceof SavePutInfoDto) {
                SavePutInfoDto savePutInfoDto = (SavePutInfoDto) joinPoint.getArgs()[0];
                for (MachIneDto machineDto : savePutInfoDto.getMachIneDtoList()) {
                    BmStorageLog bmStorageLog = new BmStorageLog();
                    bmStorageLog.setTypeId(Integer.parseInt(machineDto.getTypeId()));
                    bmStorageLog.setTypeName(machineDto.getMaCode());
                    bmStorageLog.setPreStoreNum(Objects.isNull(machineDto.getNum()) ? 0 : machineDto.getNum().intValue());
                    bmStorageLog.setInNum(Objects.isNull(machineDto.getPutInStoreNum()) ? 0 : machineDto.getPutInStoreNum().intValue());
                    bmStorageLog.setPostStoreNum(bmStorageLog.getPreStoreNum() + bmStorageLog.getInNum());
                    bmStorageLogList.add(bmStorageLog);
                }
            }
            // 新购验收任务 PurchaseMacodeInfoController.modifyManageStatus
            if (joinPoint.getArgs()[0] instanceof MaInputVO) {
                MaInputVO maInputVO = (MaInputVO) joinPoint.getArgs()[0];
                for (MaInputRecord maInputRecord : maInputVO.getInputRecordList()) {
                    BmStorageLog bmStorageLog = new BmStorageLog();
                    bmStorageLog.setTypeId(Objects.isNull(maInputRecord.getTypeId()) ? 0 : maInputRecord.getTypeId().intValue());
                    bmStorageLog.setTypeName(maInputRecord.getMaCode());
                    bmStorageLog.setPostStoreNum(Objects.isNull(maInputRecord.getNum()) ? 0: maInputRecord.getNum().intValue());
                    bmStorageLog.setInNum(Objects.isNull(maInputRecord.getInputNum()) ? 0: maInputRecord.getInputNum().intValue());
                    bmStorageLog.setPreStoreNum(bmStorageLog.getPostStoreNum() - bmStorageLog.getInNum());
                    bmStorageLog.setTaskId(String.valueOf(maInputRecord.getTaskId()));
                    bmStorageLogList.add(bmStorageLog);
                }
            }
            // 新购配件入库审核 PurchaseAccessoryController.checkInput, 因为没有出入库闭环，暂时不加库存监控日志
//            if (joinPoint.getArgs()[0] instanceof MaInputVO) {
//                MaInputVO maInputVO = (MaInputVO) joinPoint.getArgs()[0];
//                for (MaInputRecord maInputRecord : maInputVO.getInputRecordList()) {
//                    BmStorageLog bmStorageLog = new BmStorageLog();
//                    bmStorageLog.setTypeId(Objects.isNull(maInputRecord.getTypeId()) ? 0 : maInputRecord.getTypeId().intValue());
//                    bmStorageLog.setTypeName(maInputRecord.getMaCode());
//                    bmStorageLog.setPreStoreNum(Objects.isNull(maInputRecord.getNum()) ? 0: maInputRecord.getNum().intValue());
//                    bmStorageLog.setInNum(Objects.isNull(maInputRecord.getInputNum()) ? 0: maInputRecord.getInputNum().intValue());
//                    bmStorageLog.setPostStoreNum(bmStorageLog.getPreStoreNum() + bmStorageLog.getInNum());
//                    bmStorageLog.setTaskId(String.valueOf(maInputRecord.getTaskId()));
//                    bmStorageLogList.add(bmStorageLog);
//                }
//            }
            // 领料出库
            if (joinPoint.getArgs()[0] instanceof List &&
                    ((List) joinPoint.getArgs()[0]).stream().noneMatch((o -> !(o instanceof LeaseOutDetails)))) {
                List<LeaseOutDetails> leaseOutDetails = (List<LeaseOutDetails>)joinPoint.getArgs()[0];
                for (LeaseOutDetails lod : leaseOutDetails) {
                    BmStorageLog bmStorageLog = new BmStorageLog();
                    bmStorageLog.setTaskId(String.valueOf(lod.getTaskId()));
                    bmStorageLog.setTypeId(lod.getTypeId());
                    bmStorageLog.setTypeName(lod.getMaCode());
                    bmStorageLog.setPreStoreNum(lod.getPreStoreNum());
                    bmStorageLog.setOutNum(Objects.isNull(lod.getInputNum()) ? 0 : lod.getInputNum().intValue());
                    bmStorageLog.setPostStoreNum(lod.getPostStoreNum());
                    bmStorageLogList.add(bmStorageLog);
                }
            }
            if (joinPoint.getArgs()[0] instanceof LeaseOutDetails) {
                LeaseOutDetails lod = (LeaseOutDetails)joinPoint.getArgs()[0];
                BmStorageLog bmStorageLog = new BmStorageLog();
                bmStorageLog.setTaskId(String.valueOf(lod.getTaskId()));
                bmStorageLog.setTypeId(lod.getTypeId());
                bmStorageLog.setTypeName(lod.getMaCode());
                bmStorageLog.setPreStoreNum(lod.getPreStoreNum());
                bmStorageLog.setOutNum(Objects.isNull(lod.getInputNum()) ? 0 : lod.getInputNum().intValue());
                bmStorageLog.setPostStoreNum(lod.getPostStoreNum());
                bmStorageLogList.add(bmStorageLog);
            }
            // 退料接收-完成退料
            if (joinPoint.getArgs()[0] instanceof BackApplyInfo) {
                BackApplyInfo bai = (BackApplyInfo)joinPoint.getArgs()[0];
                BmStorageLog bmStorageLog = new BmStorageLog();
                bmStorageLog.setTaskId(String.valueOf(bai.getTaskId()));
                bmStorageLog.setTypeId(Integer.parseInt(bai.getTypeId()));
                bmStorageLog.setTypeName(bai.getMaCode());
                bmStorageLog.setPreStoreNum(Integer.parseInt(bai.getNum()));
                bmStorageLog.setBackNum(bai.getBackNum());
                bmStorageLog.setPassNum(bai.getPassNum());
                bmStorageLog.setMaintenanceNum(bai.getMaintenanceNum());
                bmStorageLog.setScrapNum(bai.getScrapNum());
                bmStorageLog.setPostStoreNum(bai.getPostStoreNum());
                bmStorageLogList.add(bmStorageLog);
            }
            // 修试后入库-入库操作, 因数据类型问题暂停，等更新过数据类型后再放开
//            if (joinPoint.getArgs()[0] instanceof RepairTestInputDto) {
//                RepairTestInputDto rti = (RepairTestInputDto)joinPoint.getArgs()[0];
//                BmStorageLog bmStorageLog = new BmStorageLog();
//                bmStorageLog.setTaskId(String.valueOf(rti.getTaskId()));
//                bmStorageLog.setTypeId(Integer.parseInt(rti.getTypeId()));
//                bmStorageLog.setPreStoreNum(rti.getPreStoreNum());
//                bmStorageLog.setInNum(rti.getRepairNum());
//                bmStorageLog.setPostStoreNum(rti.getPostStoreNum());
//                bmStorageLogList.add(bmStorageLog);
//            }
            // 二级库出入库, 不影响ma_type库存变化，暂时不加库存监控日志
//            if (joinPoint.getArgs()[0] instanceof TeamLeaseInfo) {
//                TeamLeaseInfo tli = (TeamLeaseInfo)joinPoint.getArgs()[0];
//                BmStorageLog bmStorageLog = new BmStorageLog();
//                bmStorageLog.setAgreementId(Integer.parseInt(tli.getAgreementId()));
//                bmStorageLog.setTypeId(Integer.parseInt(tli.getTypeId()));
//                bmStorageLog.setTypeName(tli.getTypeName());
//                bmStorageLog.setPreStoreNum(1);
//                bmStorageLog.setPreStoreNum(Integer.parseInt(tli.getNum())); //出入库数量,type=1出库,type=2退库
//                bmStorageLog.setPostStoreNum(1);
//                bmStorageLogList.add(bmStorageLog);
//            }
        }
    }


    /**
     * 获取请求的参数，放到log中
     * 
     * @param operLog 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception {
        String requestMethod = operLog.getRequestMethod();
        Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
        if (StringUtils.isEmpty(paramsMap)) {
            if ((HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))){
                String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
                operLog.setOperParam(StringUtils.substring(params, 0, 2000));
            }
        } else {
            operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
    {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0)
        {
            for (Object o : paramsArray)
            {
                if (StringUtils.isNotNull(o) && !isFilterObject(o))
                {
                    try
                    {
                        String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
                        params += jsonObj.toString() + " ";
                    }
                    catch (Exception e)
                    {
                    }
                }
            }
        }
        return params.trim();
    }

    /**
     * 忽略敏感属性
     */
    public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames)
    {
        return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
    }

    /**
     * 判断是否需要过滤的对象。
     * 
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o)
    {
        Class<?> clazz = o.getClass();
        if (clazz.isArray())
        {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        }
        else if (Collection.class.isAssignableFrom(clazz))
        {
            Collection collection = (Collection) o;
            for (Object value : collection)
            {
                return value instanceof MultipartFile;
            }
        }
        else if (Map.class.isAssignableFrom(clazz))
        {
            Map map = (Map) o;
            for (Object value : map.entrySet())
            {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}
