package com.bcxin.ins.aspect;

import com.bcxin.ins.rest.UserSupportUtil;
import com.bcxin.ins.core.entity.SysLog;
import com.bcxin.ins.spring.annotation.OperationLog;
import com.bcxin.ins.spring.cache.BaseUtils;
import com.bcxin.ins.util.toolbox.StrUtil;
import com.bcxin.ins.vo.ClientUserVo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author：huangzp
 * @date：2018/05/02 0002 9:48
 * @description： 日志切入点
 */
@Aspect
@Component
public class LogAspect {

    public LogAspect(){
        System.out.println("初始化日志切面 LogAspect=======================");
    }

    private  static  final Logger logger = LoggerFactory.getLogger(LogAspect.class);
    private static final ThreadLocal<SysLog> sysLogThreadLocal = new NamedThreadLocal<SysLog>("ThreadLocal SysLog");

    /**
     * 使用线程保存日志
     * @param map
     * @param e
     */
    private void saveLog(Map<String, Object> map,Throwable e) {
        if (map == null) {
            return;
        }
        String title = map.containsKey("title") ? map.get("title").toString() : "";
        String source = map.containsKey("source") ? map.get("source").toString() : "";
        String methodName=map.containsKey("methodName") ? map.get("methodName").toString() : "";
        String className=map.containsKey("className") ? map.get("className").toString() : "";
        Date startDate=map.containsKey("start") ? (Date)map.get("start") : null;
        Date endDate=map.containsKey("end") ? (Date)map.get("end") : null;
        Long speedTime=map.containsKey("spendTime") ? (Long)map.get("spendTime") : null;

        String ip = BaseUtils.getSubject().getSession().getHost();
        //企业或者用户id
        String companyOrUserId=null;
        ClientUserVo userdetail = UserSupportUtil.getSessionUser();
        if(userdetail!=null){
            companyOrUserId=userdetail.getOid();
        }
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        SysLog log = new SysLog();
        if(e==null) {
            log.setType(SysLog.TYPE_ACCESS);
            log.setStart(startDate);
            log.setEnd(endDate);
            log.setSpeedTime(speedTime);
        }else {
            log.setType(SysLog.TYPE_EXCEPTION);
            log.setException(e.toString());
        }
        log.setCompany_id(companyOrUserId);
        log.setClassName(className);
        log.setMethodName(methodName);
        log.setSource(source);
        log.setRemoteAddr(ip);
        log.setRequestUri(request.getRequestURI());
        log.setTitle(title);
        log.setRemoteAddr(StrUtil.getRemoteAddr(request));
        log.setUserAgent(request.getHeader("user-agent"));
        log.setRequestUri(request.getRequestURI());
        Map<String, String[]> params = request.getParameterMap();
        if(params.size()<100){
            log.setParams(request.getParameterMap());
        }
        log.setMethod(request.getMethod());

        if(e!=null){
            //新增日志到队列
            LogManager.addLog(log);
        }else {
            sysLogThreadLocal.set(log);
        }
//            // 异步保存日志
//            try {
//                new Thread() {
//                    @Override
//                    public void run() {
//                        sysLogService.save(log);
//                    }
//                }.start();
//            } catch (Exception e1) {
//                e1.printStackTrace();
//            }
    }

     /**
     * Controller层切点
     */
     @Pointcut("@annotation(com.bcxin.ins.spring.annotation.OperationLog)")
     public void controllerAspect() {
     }

    /**
     * 操作异常记录
     *@descript
     *@param point
     *@version 1.0
     */
    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public  void doAfterThrowing(JoinPoint point, Throwable e) {
        try {
            Map<String, Object> map =getControllerMethodDescription(point);
            saveLog(map,e);
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }

    /**
     * 前置通知
     * @param joinPoint
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) {
//         System.out.println("==========执行controller前置通知===============");
         if(logger.isInfoEnabled()){
                 logger.info("before " + joinPoint);
         }
    }

    /**
     * 环绕通知 用于拦截Controller层记录用户的操作
     *
     * @param point 切点
     */
    @Around("controllerAspect()")
    public Object doAroundController(ProceedingJoinPoint point) {
        Object result = null;
        // 执行方法名
        String methodName = point.getSignature().getName();
        String className = point.getTarget().getClass().getName();
        Map<String, Object> map = null;
        Date start;
        Date end;
        Long time = 0L;
        // 当前用户
        try {
            map=getControllerMethodDescription(point);
            // 执行方法所消耗的时间
            start = new Date();
            result = point.proceed();
            end = new Date();
            time = end.getTime() - start.getTime();

            map.put("start",start);
            map.put("end",end);
            map.put("spendTime",time);
            map.put("methodName",methodName);
            map.put("className",className);
            saveLog(map,null);

        } catch (Throwable e) {
            //记录本地异常日志
            logger.error("====通知异常====");
            logger.error("异常信息:{}", e.getMessage());
            throw new ExceptionInInitializerError(e);
        }
        return result;
    }

    /**
     * 方法调用后
     * @param joinPoint
     */
    @AfterReturning("controllerAspect()")
    public void doAfterInServiceLayer(JoinPoint joinPoint) {
        if(sysLogThreadLocal.get()!=null) {
            SysLog log=sysLogThreadLocal.get();
            //获取request
            //HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            //获取response
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
            System.out.print(response.getStatus());
            log.setResponseStatus(response.getStatus()+"");
            //新增日志到队列
            LogManager.addLog(log);
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    @SuppressWarnings("rawtypes")
    public Map<String, Object> getControllerMethodDescription(JoinPoint joinPoint)  throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String params = "";
//        if (joinPoint.getArgs() !=  null && joinPoint.getArgs().length > 0) {
//            for ( int i = 0; i < arguments.length; i++) {
//                params += JSONUtil.toJsonStr(joinPoint.getArgs()[i]) + ";";
//            }
//            map.put("params", params);
//        }

        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    map.put("title", method.getAnnotation(OperationLog.class).title());
                    map.put("source", method.getAnnotation(OperationLog.class).source().getKey());
                    break;
                }
            }
        }
        return map;
    }
}
