package com.inspect.common.log.aspect;

import com.alibaba.fastjson.JSON;
import com.inspect.base.core.utils.ServletUtils;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.core.utils.ip.IpUtils;
import com.inspect.common.log.annotation.Log;
import com.inspect.common.log.enums.BizCode;
import com.inspect.common.log.service.AsyncLogService;
import com.inspect.common.security.utils.SecurityUtils;
import com.inspect.system.base.domain.SysOperateLog;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

@Aspect
@Component
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    final AsyncLogService asyncLogService;

    @Autowired
    public LogAspect(AsyncLogService asyncLogService) {
        this.asyncLogService = asyncLogService;
    }

    @AfterReturning(
            pointcut = "@annotation(controllerLog)",
            returning = "jsonResult"
    )
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
        this.handleLog(joinPoint, controllerLog, null, jsonResult);
    }

    @AfterThrowing(
            value = "@annotation(controllerLog)",
            throwing = "e"
    )
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
        this.handleLog(joinPoint, controllerLog, e, (Object) null);
    }

    protected void handleLog(JoinPoint joinPoint, Log controllerLog, Exception e, Object jsonResult) {
        try {
            SysOperateLog operateLogger = SysOperateLog
                    .builder()
                    .status(e == null ? BizCode.SUCCESS.ordinal() : BizCode.FAIL.ordinal())
                    .errorMsg(e == null ? null : StringUtils.substring(e.getMessage(), 0, 2000))
                    .operateIp(IpUtils.getIpAddr(ServletUtils.getRequest()))
                    .operateUrl(ServletUtils.getRequest().getRequestURI())
                    .operateName(SecurityUtils.getUsername())
                    .method(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")
                    .requestMethod(ServletUtils.getRequest().getMethod())
                    .build();
            this.getControllerMethodDescription(joinPoint, controllerLog, operateLogger, jsonResult);
            operateLogger.setAuditLog(controllerLog.isAudit());
            this.asyncLogService.saveSysLog(operateLogger);
        } catch (Exception exception) {
            logger.error("[GABRIEL] Handle log Exception: {}", exception.getMessage());
        }
    }

    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperateLog operLog, Object jsonResult) throws Exception {
        operLog.setBusinessType(log.businessType().ordinal());
        operLog.setTitle(log.title());
        operLog.setOperateType(log.operatorType().ordinal());
        if (log.isSaveRequestData()) {
            this.setRequestValue(joinPoint, operLog);
        }

        if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
            operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
        }

    }

    private void setRequestValue(JoinPoint joinPoint, SysOperateLog operateLog) throws Exception {
        String requestMethod = operateLog.getRequestMethod();
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
            String params = this.argsArrayToString(joinPoint.getArgs());
            operateLog.setOperateParam(StringUtils.substring(params, 0, 2000));
        }

    }

    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null) {
            for (Object param : paramsArray) {
                if (StringUtils.isNotNull(param) && !this.isFilterObject(param)) {
                    try {
                        Object jsonObj = JSON.toJSON(param);
                        params.append(jsonObj.toString()).append(" ");
                    } catch (Exception exception) {
                        logger.error("[GABRIEL] args to string exception: {}", exception.getMessage());
                    }
                }
            }
        }

        return params.toString().trim();
    }

    public boolean isFilterObject(Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else {
            Iterator iterator;
            Object value;
            if (Collection.class.isAssignableFrom(clazz)) {
                Collection collection = (Collection) o;
                iterator = collection.iterator();
                if (iterator.hasNext()) {
                    value = iterator.next();
                    return value instanceof MultipartFile;
                }
            } else if (Map.class.isAssignableFrom(clazz)) {
                Map map = (Map) o;
                iterator = map.entrySet().iterator();
                if (iterator.hasNext()) {
                    value = iterator.next();
                    Entry entry = (Entry) value;
                    return entry.getValue() instanceof MultipartFile;
                }
            }

            return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult;
        }
    }
}
