package com.greenorange.promotion.aop;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.greenorange.promotion.annotation.SysLog;
import com.greenorange.promotion.model.dto.fileInfo.UploadFileRequest;
import com.greenorange.promotion.model.entity.SysOperLog;
import com.greenorange.promotion.service.log.SysOperLogService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 切面处理类，记录操作日志到数据库
 */
@Aspect
@Component
@Slf4j
public class OperateLogAspect {

    @Resource
    private SysOperLogService sysOperLogService;

    // 为了记录方法的执行时间
    ThreadLocal<Long> startTime = new ThreadLocal<>();


    @Before("@annotation(sysLog)")
    public void beforeMethod(JoinPoint joinPoint, SysLog sysLog){
        startTime.set(System.currentTimeMillis());
    }


    /**
     * 正常返回通知，拦截用户操作日志，连接点正常执行完成后执行， 如果连接点抛出异常，则不会执行
     * @param joinPoint 切入点
     * @param result    返回结果
     */
    @AfterReturning(value = "@annotation(sysLog)", returning = "result")
    public void saveOperateLog(JoinPoint joinPoint, SysLog sysLog, Object result) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取操作
            SysOperLog sysOperLog = new SysOperLog();
            sysOperLog.setTitle(sysLog.title());//设置模块名称
            sysOperLog.setContent(sysLog.content());//设置日志内容
            // 将入参转换成json
            String params = argsArrayToString(joinPoint.getArgs());
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName + "()";
            sysOperLog.setMethod(methodName); //设置请求方法
            sysOperLog.setRequestMethod(request.getMethod());//设置请求方式
            sysOperLog.setRequestParam(params); // 请求参数
            sysOperLog.setResponseResult(JSON.toJSONString(result)); // 返回结果
            // 从request中取出账号
            String userAccount = (String) request.getAttribute("userAccount");
            sysOperLog.setOperName(userAccount); // 获取用户名（真实环境中，肯定有工具类获取当前登录者的账号或ID的，或者从token中解析而来）
            String ip = getIp(request);
            sysOperLog.setIp(ip); // IP地址
            String ipLocation = getIpLocation(ip);
            sysOperLog.setIpLocation(ipLocation); // IP归属地（真是环境中可以调用第三方API根据IP地址，查询归属地）
            sysOperLog.setRequestUrl(request.getRequestURI()); // 请求URI
            sysOperLog.setOperTime(new Date()); // 时间
            sysOperLog.setStatus(0);//操作状态（0正常 1异常）
            Long takeTime = System.currentTimeMillis() - startTime.get();//记录方法执行耗时时间（单位：毫秒）
            sysOperLog.setTakeTime(takeTime);
            //插入数据库
            sysOperLogService.save(sysOperLog);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     */
    @AfterThrowing(pointcut = "execution(* com.greenorange.promotion.controller..*.*(..))", throwing = "e")
    public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        SysOperLog sysOperLog = new SysOperLog();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName + "()";
            // 获取操作
            SysLog sysLog = method.getAnnotation(SysLog.class);
            if (sysLog == null) return ;
            sysOperLog.setTitle(sysLog.title());//设置模块名称
            sysOperLog.setContent(sysLog.content());//设置日志内容
            // 将入参转换成json
            String params = argsArrayToString(joinPoint.getArgs());
            sysOperLog.setMethod(methodName); //设置请求方法
            sysOperLog.setRequestMethod(request.getMethod());//设置请求方式
            sysOperLog.setRequestParam(params); // 请求参数
            // 从request中取出账号
            String userAccount = (String) request.getAttribute("userAccount");
            sysOperLog.setOperName(userAccount); // 获取用户名（真实环境中，肯定有工具类获取当前登录者的账号或ID的，或者从token中解析而来）
            String ip = getIp(request);
            sysOperLog.setIp(ip); // IP地址
            String ipLocation = getIpLocation(ip);
            sysOperLog.setIpLocation(ipLocation); // IP归属地（真是环境中可以调用第三方API根据IP地址，查询归属地）
            sysOperLog.setRequestUrl(request.getRequestURI()); // 请求URI
            sysOperLog.setOperTime(new Date()); // 时间
            sysOperLog.setStatus(1);//操作状态（0正常 1异常）
            sysOperLog.setErrorMsg(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));//记录异常信息
            //插入数据库
            sysOperLogService.save(sysOperLog);
        } catch (Exception e2) {
            e2.printStackTrace();
        }
    }

    /**
     * 转换异常信息为字符串
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        message = substring(message,0 ,2000);
        return message;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray)
    {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (o != null) {
                    try {
                        Object jsonObj = JSON.toJSON(o);
                        params += jsonObj.toString() + " ";
                    } catch (Exception e) {
//                        e.printStackTrace();
                        log.warn("JSON参数解析出现异常");
                    }
                }
            }
        }
        return params.trim();
    }

    //字符串截取
    public String substring(String str, int start, int end) {
        if (str == null) {
            return null;
        } else {
            if (end < 0) {
                end += str.length();
            }

            if (start < 0) {
                start += str.length();
            }

            if (end > str.length()) {
                end = str.length();
            }

            if (start > end) {
                return "";
            } else {
                if (start < 0) {
                    start = 0;
                }

                if (end < 0) {
                    end = 0;
                }
                return str.substring(start, end);
            }
        }
    }

    /**
     * 转换request 请求参数
     * @param paramMap request获取的参数数组
     */
    public Map<String, String> convertMap(Map<String, String[]> paramMap) {
        Map<String, String> returnMap = new HashMap<>();
        for (String key : paramMap.keySet()) {
            returnMap.put(key, paramMap.get(key)[0]);
        }
        return returnMap;
    }

    //根据HttpServletRequest获取访问者的IP地址
    public String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }



    public String getIpLocation(String ip) {
//        try {
//            // 发送请求到ip-api服务
//            String url = "http://ip-api.com/json/" + ip + "?lang=zh-CN"; // 获取中文结果
//            URL obj = new URL(url);
//            HttpURLConnection con = (HttpURLConnection) obj.openConnection();
//            con.setRequestMethod("GET");
//
//            BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
//            String inputLine;
//            StringBuffer response = new StringBuffer();
//
//            while ((inputLine = in.readLine()) != null) {
//                response.append(inputLine);
//            }
//            in.close();
//
//            // 解析JSON返回结果
//            JSONObject jsonResponse = JSONObject.parseObject(response.toString());
//            String country = jsonResponse.getString("country");    // 国家
//            String region = jsonResponse.getString("regionName");  // 省
//            String city = jsonResponse.getString("city");          // 城市
//
//            return country + "-" + region + "-" + city;  // 返回位置
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
        return "未知";
    }

}