package cn.lihongjie.coal.filter;

import cn.lihongjie.coal.common.Constants;
import cn.lihongjie.coal.common.RequestUtils;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.ip.IpQueryService;
import cn.lihongjie.coal.loginUser.service.LoginUserService;
import cn.lihongjie.coal.resource.dto.ResourceDto;
import cn.lihongjie.coal.sysconfig.service.SysConfigService;
import cn.lihongjie.coal.syslog.service.SysLogService;

import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

import javax.crypto.Mac;

/** 请求签名验证 */
@Component
@Order(10)
@Slf4j
public class SignFilter extends OncePerRequestFilter {
    public static final int TIME_DIFF_MS = 1000 * 60 * 2;
    @Autowired ObjectMapper objectMapper;
    @Autowired SysConfigService sysConfigService;

    @Autowired SysLogService sysLogService;
    @Autowired IpQueryService ipQueryService;
    @Autowired LoginUserService loginUserService;

    @Autowired RedisTemplate<String, String> redisTemplate;

    private static String getFieldFromHeaderOrQs(HttpServletRequest request, String name) {
        return StringUtils.defaultIfEmpty(request.getHeader(name), request.getParameter(name));
    }

    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        if (!sysConfigService.isEnable(Constants.SYSCONFIG_ENABLE_REQUEST_SIGN)) {
            doFilter(request, response, filterChain);
            return;
        }


        if (request.getAttribute(Constants.HTTP_ATTR_RESOURCE) != null) {
            if (BooleanUtils.isFalse(
                    ((ResourceDto) request.getAttribute(Constants.HTTP_ATTR_RESOURCE))
                            .getSignCheck())) {
                doFilter(request, response, filterChain);
                return;


            }
        }


        String configVal =
                sysConfigService.getConfigVal(Constants.SYSCONFIG_REQUEST_SIGN_IGNORE_URLS);

        if (StringUtils.isNotEmpty(configVal)) {
            AntPathMatcher antPathMatcher = new AntPathMatcher();
            String[] ignoreUrls = configVal.split(",");
            for (String ignoreUrl : ignoreUrls) {
                if (antPathMatcher.match(ignoreUrl, request.getRequestURI())) {
                    doFilter(request, response, filterChain);
                    return;
                }
            }
        }

        StopWatch stopWatch = new StopWatch();

        stopWatch.start("基本请求头");

        StringBuilder sb = new StringBuilder();

        // 请求方法

        sb.append(StringUtils.defaultString(request.getMethod().toUpperCase()));
        sb.append(StringUtils.defaultString("\n"));

        // 请求地址
        sb.append(
                StringUtils.defaultString(
                        ObjectUtils.defaultIfNull(
                                getFieldFromHeaderOrQs(request, Constants.HTTP_HEADER_CLIENT_URL),
                                request.getRequestURI())));
        sb.append(StringUtils.defaultString("\n"));

        // 请求参数
        sb.append(StringUtils.defaultString(request.getQueryString()));
        sb.append(StringUtils.defaultString("\n"));

        // 请求头

        String token =
                ObjectUtils.defaultIfNull(
                        getFieldFromHeaderOrQs(request, Constants.HTTP_HEADER_TOKEN), "anonymous");
        String ts = getFieldFromHeaderOrQs(request, Constants.HTTP_HEADER_TS);
        if (StringUtils.isEmpty(ts)) {
            RequestUtils.writeResponse(
                    new BizException(Constants.HTTP_HEADER_TS + " 请求头缺失"), response);
            return;
        }

        String random = getFieldFromHeaderOrQs(request, Constants.HTTP_HEADER_CLIENT_RANDOM);
        if (StringUtils.isEmpty(random)) {
            RequestUtils.writeResponse(
                    new BizException(Constants.HTTP_HEADER_CLIENT_RANDOM + " 请求头缺失"), response);
            return;
        }
        long tsi = 0;

        try {

            tsi = Long.parseLong(ts);
        } catch (Exception e) {
            RequestUtils.writeResponse(
                    new BizException(Constants.HTTP_HEADER_TS + " 请求头格式错误"), response);
            return;
        }

        long current = System.currentTimeMillis();

        if (Math.abs(tsi - current) > TIME_DIFF_MS) {
            RequestUtils.writeResponse(
                    new BizException(
                            Constants.HTTP_HEADER_TS
                                    + " 客户端时间误差过大,请校准时间. 服务器时间为: "
                                    + LocalDateTime.now()),
                    response);
            return;
        }

        sb.append(StringUtils.defaultString(token));

        sb.append(StringUtils.defaultString("\n"));

        sb.append(StringUtils.defaultString(ts));

        sb.append(StringUtils.defaultString("\n"));

        sb.append(StringUtils.defaultString(random));

        sb.append(StringUtils.defaultString("\n"));

        stopWatch.stop();

        stopWatch.start("sha256Hex");

        String sha256Hex;
        MediaType mediaType =
                MediaType.parseMediaType(
                        ObjectUtils.defaultIfNull(
                                request.getContentType(),
                                MediaType.APPLICATION_OCTET_STREAM_VALUE));
        // 文件上传不对body进行签名
        if (mediaType.isCompatibleWith(MediaType.MULTIPART_FORM_DATA)) {

            sha256Hex = DigestUtils.sha256Hex("").toUpperCase();

        } else {
            sha256Hex = DigestUtils.sha256Hex(request.getInputStream()).toUpperCase();
        }

        sb.append(StringUtils.defaultString(sha256Hex));
        sb.append(StringUtils.defaultString("\n"));

        stopWatch.stop();

        stopWatch.start("签名");

        Mac mac =
                HmacUtils.getInitializedMac(
                        HmacAlgorithms.HMAC_SHA_256, token.getBytes(StandardCharsets.UTF_8));
        mac.update(sb.toString().getBytes(StandardCharsets.UTF_8));
        String sign = Hex.encodeHexString(mac.doFinal()).toUpperCase();

        stopWatch.stop();

        String clientSign = getFieldFromHeaderOrQs(request, Constants.HTTP_HEADER_CLIENT_SIGN);

        if (!sign.equals(clientSign)) {
            log.debug("key: {} \ndata:{}", token, sb);
            log.warn("签名错误: {} {}", clientSign, sign);
            sysLogService.saveSysLog(request, "签名模块", "签名错误", clientSign + " " + sign);
            RequestUtils.writeResponse(
                    new BizException(Constants.HTTP_HEADER_CLIENT_SIGN + " 请求头签名错误"), response);
            return;
        }

        stopWatch.start("重复请求");
        String cr = redisTemplate.opsForValue().get(Constants.CACHE_CLIENT_RANDOM_PREFIX + random);

        if (StringUtils.equals(cr, "1")) {
            sysLogService.saveSysLog(request, "签名模块", "重复请求", "");
            RequestUtils.writeResponse(new BizException("请求已过期, 请刷新页面重试"), response);
            return;
        } else {
            redisTemplate
                    .opsForValue()
                    .set(
                            Constants.CACHE_CLIENT_RANDOM_PREFIX + random,
                            "1",
                            TIME_DIFF_MS,
                            TimeUnit.MILLISECONDS);
        }

        stopWatch.stop();

        if (request instanceof CacheBodyRequestWrapper cacheBodyRequestWrapper) {
            cacheBodyRequestWrapper.reset();
        }

        log.debug("签名验证通过: {}", stopWatch.prettyPrint());

        doFilter(request, response, filterChain);
    }
}
