package com.sdtool.common.base;

import com.sdtool.common.api.NoSqlKeysService;
import com.sdtool.common.api.OpLogService;
import com.sdtool.common.datas.ERPHeader;
import com.sdtool.common.datas.HttpCode;
import com.sdtool.common.datas.RedisKeys;
import com.sdtool.common.datas.TokenUserFrom;
import com.sdtool.common.entity.base.InvokeCallParams;
import com.sdtool.common.entity.site.ERPTokenUser;
import com.sdtool.common.entity.site.LoginRequest;
import com.sdtool.common.entity.system.KeyValuePair;
import com.sdtool.common.entity.system.OpLog;
import com.sdtool.common.tool.ERPUtils;
import com.dySweetFishPlugin.redis.RedisService;
import com.dySweetFishPlugin.sql.RMapUtils;
import com.dySweetFishPlugin.tool.lang.DateUtil;
import com.sweetfish.convert.json.JsonConvert;
import com.sweetfish.net.http.*;
import com.sweetfish.util.AnyValue;
import com.sweetfish.util.AutoLoad;
import com.sweetfish.util.ResourceFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;

/**
 * 所有的HttpServlet继承此类给rest做http容器
 * 其实主要是几个token的不同
 */
@AutoLoad(false)
@HttpUserType(ERPTokenUser.class)
public abstract class BaseHttpServlet extends HttpServlet {

    protected final Logger logger = LogManager.getLogger(this.getClass().getSimpleName());

    @Resource(name = "server")
    private ResourceFactory rf;

    @Resource
    private JsonConvert jsonConvert;

    @Resource
    protected OpLogService opLogService;

    private boolean servletWriteLog;

    @Resource
    private NoSqlKeysService keysService;

    @Resource
    private RedisService redisService;

    /**
     * 有一些前端定时的自动操作，如果都记日志，数据有些大，排除掉
     */
    private static final String[] excludeRequest = {"/idp/callProcess/ImageProcess_GetDesignerStateList"};

    @Override
    public void init(HttpContext context, AnyValue config) {
        super.init(context, config);

        ERPServlet es = this.getClass().getAnnotation(ERPServlet.class);
        this.servletWriteLog = es.writeLog();
    }

    /**
     * 获取当前用户
     *
     * @param request http请求
     * @return 当前用户信息
     */
    public ERPTokenUser currentUser(HttpRequest request) {
        //先判断请求中是否有
        ERPTokenUser user = request.currentUser();
        if (user != null) {
            return user;
        }

        //没有则判断token
        String token = ERPUtils.parseHttpToken(request);

        String userFrom = ERPUtils.parseHttpFrom(request);


        int userFromInt = (StringUtils.isEmpty(userFrom)) ? TokenUserFrom.PC_ERP_ADMIN.getValue() : Integer.parseInt(userFrom);

        String tokenCommonKey = rf.find("property." + TokenUserFrom.getTokenKey(userFromInt), String.class);
        return ERPUtils.parseERPUserFromToken(token, tokenCommonKey, jsonConvert);
    }

    @Override
    protected void preExecute(HttpRequest request, HttpResponse response) throws IOException {
        //注册一个监听器
        response.recycleListener((req, resp) -> {
            //记录超过5秒的请求
            long e = System.currentTimeMillis() - req.getCreatetime();
            if (e > 50000) {
                logger.info("http-execute-cost-time: " + e + " ms. request = " + req);
            }
        });
        //设置当前用户
        ERPTokenUser userInfo = currentUser(request);
        request.setCurrentUser(userInfo);
        response.nextEvent();
    }

    @Override
    public boolean checkRequestCount(HttpRequest request, HttpResponse response, int limitCount, long limitTime) {
        String ip = request.getRemoteAddr();
        String url = request.getRequestURI();

        String key = keysService.getRedisKey(RedisKeys.KEY_REQUESTLIMIT, 0L, true).concat(url).concat(ip);
        long count = redisService.incrby(key, 1);
        if (count == 1) {
            redisService.pexpire(key, limitTime);
        }
        if (count > limitCount) {
            logger.info("用户IP[" + ip + "]访问地址[" + url + "]超过了限定的次数[" + limitCount + "]");
            response.finishJson(RMapUtils.error(HttpCode.MULTI_STATUS.value(), "请求超过了限定的次数"));
            return false;
        }
        return true;
    }

    @Override
    public void responseMethodReturn(HttpRequest request, HttpResponse response, String requestMethod, String actMethod) {
        response.finishJson(RMapUtils.error(HttpCode.BAD_REQUEST.value(), request.getRequestURI() + "Method(" + requestMethod + ") Error (" + actMethod + ")"));
    }

    public void logRequestMethod(HttpRequest request, HttpResponse response, int logging) {
        if (servletWriteLog) {
            ERPTokenUser userInfo = request.currentUser();

            //记录操作日志
            //CompletableFuture.runAsync(() -> {
            OpLog opLog = new OpLog();

            if (userInfo != null) {
                opLog.setUserId(userInfo.getId());
                opLog.setUserName(userInfo.getUserName());
            } else {
                opLog.setUserId(-1L);
                opLog.setUserName("未登录用户");
            }
            opLog.setClientIp(request.getRemoteAddr());
            Date nowDate = new Date();
            opLog.setOpDateStr(DateUtil.dateToStr(nowDate, "yyyy-MM-dd HH:mm:ss"));
            opLog.setOpDate(nowDate.getTime());
            opLog.setUrl(request.getRequestURI());

            request.getHeaders().forEach((k, v) -> opLog.getHeaders().add(new KeyValuePair(k, request.getHeader(k))));

            if (!"/system/login".equalsIgnoreCase(request.getRequestURI())) {
                //登录不记录参数，有密码
                if (logging == 4) {
                    //通用调脚本方法有些特殊，如果只记录 callXXX，则不方便定位问题出错的地方，但如果这里又转换一次数据，有些不甘
                    //是否可以通过正则表达式直接取出 businessMethod 后面引号 逗号中的字符串？
                    String callBody = request.getBodyUTF8();
                    if (!StringUtils.isEmpty(callBody)) {
                        InvokeCallParams callParams = jsonConvert.convertFrom(InvokeCallParams.class, callBody);
                        String methodRequest = request.getRequestURI() + "/" + callParams.getBusinessMethod();
                        if (Arrays.stream(excludeRequest).noneMatch(methodRequest::equalsIgnoreCase)) {
                            opLog.setUrl(request.getRequestURI() + "/" + callParams.getBusinessMethod());
                        }
                    }
                    opLog.getParams().add(new KeyValuePair("body", request.getBodyUTF8()));
                } else if (logging >= 2) {
                    opLog.getParams().add(new KeyValuePair("body", request.getBodyUTF8()));
                } else {
                    for (String key : request.getParameterNames()) {
                        if ("password".equalsIgnoreCase(key)) {
                            opLog.getParams().add(new KeyValuePair(key, "********"));
                        } else {
                            opLog.getParams().add(new KeyValuePair(key, request.getParameter(key)));
                        }
                    }
                }
            } else {
                for (String key : request.getParameterNames()) {
                    if ("user".equalsIgnoreCase(key)) {
                        LoginRequest tmpUser = jsonConvert.convertFromO(LoginRequest.class, request.getParameter(key));
                        opLog.getParams().add(new KeyValuePair("登录账号", tmpUser.getLoginName()));
                        opLog.getParams().add(new KeyValuePair("登录IP", request.getRemoteAddr()));
                    } else {
                        opLog.getParams().add(new KeyValuePair(key, request.getParameter(key)));
                    }
                }
            }

            opLogService.put(opLog, request.getLongHeader(ERPHeader.HTTPHEADER_SUPPLIER, 0));
        }
    }

    //其它方法authenticate,由子类继承
}
