package org.shunt.aop;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.shunt.common.JWTAuthUtil;
import org.shunt.common.XmoseException;
import org.shunt.common.XmoseRequest;
import org.shunt.common.XmoseResponse;
import org.shunt.contorller.response.AuthorDTO;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.extern.slf4j.Slf4j;

@Aspect
@Component
@Order(2)
@Slf4j
public class AuthorAop {

    public static final String ERROR_FORMAT = "访问接口失败:类%s, 方法%s,参数%s,异常%s";
    private static final ObjectMapper mapper = new ObjectMapper();
    //请求处理开始时间（线程内变量）
    private static final ThreadLocal<Long> requestStartTime = new ThreadLocal<>();
    public static final String AUTH_TOKEN = "token";

    @Pointcut("@within(org.shunt.aop.ShuntApi)")
    public void webaop() {
    }

    @SuppressWarnings("rawtypes")
    @Around(value = "webaop()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        Boolean mustVertify = true;

        XmoseResponse result = null;
        Object[] params = pjp.getArgs();
        String token = getToken();
        Method method = getMethod(pjp);
        ApiAuth adminAuth = method.getAnnotation(ApiAuth.class);
        ApiAuthIgnore authIgnore = method.getAnnotation(ApiAuthIgnore.class);


        if (Objects.isNull(adminAuth) || !Objects.isNull(authIgnore)) {
            mustVertify = false;
        }
        requestStartTime.set(System.currentTimeMillis());
        try {

            if (StringUtils.hasText(token) && Boolean.TRUE.equals(mustVertify)) {
            	AuthorDTO auth = new AuthorDTO();
                JWTAuthUtil.parseJWT(token, auth);
            }
            if (adminAuth != null && !StringUtils.hasText(token)) {
                result = XmoseResponse.fail(XmoseResponse.CODE_TOKEN_EMPTY, "未授权");
                return result;
            }
            Object object = pjp.proceed(params);

            if (StringUtils.hasText(token)) {
                HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
                response.setHeader(AUTH_TOKEN, token);
            }
            if (object instanceof XmoseResponse) {
                result = (XmoseResponse) object;
            } else {
                return object;
            }

        } catch (ConstraintViolationException ex) {
            List<String> messages = ex.getConstraintViolations().stream().map(violation -> {
                //node是路径最后一个节点,即参数名
                Path.Node lastNode = null;
                for (Path.Node node : violation.getPropertyPath()) {
                    lastNode = node;
                }
                return (lastNode == null ? "" : lastNode.getName()) + violation.getMessage();
            }).collect(Collectors.toList());
            log.error(messages.toString());
            result = XmoseResponse.fail(XmoseResponse.CODE_UNSUPPORTED,  "参数不正确");
        } catch (XmoseException ex) {
            result = XmoseResponse.fail(ex.getStatus(), ex.getMsg2Dev());
            printErrorMessage(ex, method.getDeclaringClass().getName(), method.getName(), params);
        } catch (Exception e) {
            e.printStackTrace();
            result = XmoseResponse.fail(XmoseResponse.CODE_INTERNEL, "系统异常");
            printErrorMessage(e, method.getDeclaringClass().getName(), method.getName(), params);
        } finally {
            String targetClassName = pjp.getTarget().getClass().getName();
            String targetMethodName = pjp.getSignature().getName();
            if (!"health".endsWith(targetMethodName)) {
                log.info("请求[{}.{}]的处理耗时：{}毫秒", targetClassName, targetMethodName, System.currentTimeMillis() - requestStartTime.get());
            }
        }

        return result;
    }


    private String getToken() {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        String token = "";
        //优先从url中获取token
        token = request.getParameter(AUTH_TOKEN);
        //尝试从参数中获取身份token
        if (!StringUtils.hasText(token)) {
            token = getFromHeader(request);
        }
        //尝试从Cookie中获取身份token
        if (!StringUtils.hasText(token)) {
            token = getFromCookie(request, AUTH_TOKEN);
        }
        //尝试从Cookie中获取身份token
        if (!StringUtils.hasText(token)) {
            token = (String) request.getSession().getAttribute(AUTH_TOKEN);
        }
        return token;
    }

    private String getFromHeader(HttpServletRequest request) {
        return request.getHeader(AUTH_TOKEN);
    }
    public static String getFromCookie(HttpServletRequest request, String key) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(key)) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }
    /*
     * 打印日志
     */
    @AfterReturning(value = "webaop()", argNames = "point,retVl", returning = "retVl")
    public void doAfterReturning(JoinPoint point, Object retVl) {
        try {
            RequestAttributes ra = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes sra = (ServletRequestAttributes) ra;
            HttpServletRequest request = sra.getRequest();
            Map<String, String> map = this.getHeadersInfo(request);
            log.info("请求处理结束:----------------------------------------------------------");
            log.info("请求类:{}", point.getTarget().getClass().getName());
            log.info("请求方法:{}", getMethod(point).getName());
            log.info("请求head:{}", map);
            Object[] params = point.getArgs();
            if (params.length != 0) {
                for (Object object : params) {
                    if (object instanceof XmoseRequest) {
                        log.info("请求参数：{}", mapper.writeValueAsString(params[0]));
                        break;
                    }
                }
            }
            log.info("返回参数：{}", mapper.writeValueAsString(retVl));
        } catch (Exception e) {
            log.error("内部异常:{},{}", e.getMessage(), e);
        }
    }

    public Method getMethod(JoinPoint pjp) {
        Method method;
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        method = signature.getMethod();
        return method;
    }

    private Map<String, String> getHeadersInfo(HttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        Enumeration<?> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }

    private void printErrorMessage(Exception e, String className, String methodName, Object params) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw, true));
        String str = sw.toString();
        String content;
        try {
            content = String.format(ERROR_FORMAT, className, methodName, mapper.writeValueAsString(params), str);
            log.error(content);
        } catch (JsonProcessingException e1) {
            e1.printStackTrace();
        }
    }
}
