package com.ruoyi.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.google.gson.Gson;
import com.ruoyi.common.core.domain.vo.TransportHeader;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.gateway.config.properties.CaptchaProperties;
import com.ruoyi.gateway.service.ValidateCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;

import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;

import static com.ruoyi.common.core.constant.ThirdLoginConstants.IS_THIRD;
import static com.ruoyi.common.core.constant.ThirdLoginConstants.THIRD_FLAG;

/**
 * 验证码过滤器
 *
 * @author ruoyi
 */
@Component
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
{
//    private final static String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" };
    private final static String[] VALIDATE_URL = new String[] { "/auth/oauth/token", "/auth-website/register" };

    @Autowired
    private ValidateCodeService validateCodeService;

    @Autowired
    private CaptchaProperties captchaProperties;

    private static final String CODE = "code";

    private static final String UUID = "uuid";

    private final Gson gson = new Gson();

    @Override
    public GatewayFilter apply(Object config)
    {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

            // 非登录/注册请求或验证码关闭，不处理
            if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
            {
                return chain.filter(exchange);
            }

            try
            {
                String rspStr = resolveBodyFromRequest(request);
                // json格式参数可以直接使用
//                JSONObject obj = JSON.parseObject(rspStr);

                // 使用equals进行字符串比较（原代码使用!=会错误）
                // 增加格式校验和必填校验
                // 补充CRC校验逻辑和SM4解密
                // 根据接口类型处理Token必填逻辑
//                if (StringUtils.isEmpty(rspStr) && rspStr.indexOf("Header") > -1
//                        && rspStr.indexOf("Body") > -1) {
//
//                    TransportHeader header = gson.fromJson(rspStr, TransportHeader.class);
//
//                    // 校验系统标识（平台企业编码为6位数字，省级系统为行政区划代码）
//                    if(!header.getSystemId().matches("\\d{6}")) {
//                        throw new IllegalArgumentException("无效系统标识");
//                    }
//
//                    // 来源校验（平台企业或省级系统）
//                    if(!header.getSource().matches("[0-9]{6}|[A-Za-z0-9]{6}")) {
//                        throw new IllegalArgumentException("消息来源标识错误");
//                    }
//
//                    // 目标校验（部级系统固定为999999）
//                    if(!"999999".equals(header.getTarget())) {
//                        throw new IllegalArgumentException("消息目标必须指向部级系统");
//                    }
//
//                    // 版本号校验
//                    if(!"V1".equals(header.getVersion())) {
//                        throw new IllegalArgumentException("接口版本号错误");
//                    }
//
//                    // 请求时间格式校验
//                    if(!header.getReqTime().matches("\\d{14}")) {
//                        throw new IllegalArgumentException("时间格式错误");
//                    }
//
//                    // 请求类型校验
//                    if(!"0".equals(header.getReqType()) && !"1".equals(header.getReqType())) {
//                        throw new IllegalArgumentException("非法请求类型");
//                    }
//
//                    // 流水号校验（行政区划+日期+序列号）
//                    if(!header.getSerialNumber().matches("\\d{6}\\d{8}\\d{12}")) {
//                        throw new IllegalArgumentException("流水号格式错误");
//                    }
//                }

                // json参数转换为表单格式参数。
                JSONObject obj = new JSONObject();
                String grantType = "";
                if (!StringUtils.isEmpty(rspStr) && rspStr.contains("Header") && rspStr.contains("Body")) {
                    grantType = "client_credentials";
                } else {
                    obj = handleFormURLEncoded(rspStr);
                    grantType = obj.getString("grant_type");
                }
                //客户端模式放行
                if("client_credentials".equals(grantType) || "refresh_token".equals(grantType)  || "wx".equals(grantType) || "mobile".equals(grantType)) {
                    return chain.filter(exchange);
                }
                validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
            }
            catch (Exception e)
            {
                return ServletUtils.webFluxResponseWriter(exchange.getResponse(), HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value());
            }
            return chain.filter(exchange);
        };
    }

    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)
    {
        // 获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        return bodyRef.get();
    }

    private JSONObject handleFormURLEncoded(String param) {
        JSONObject jsonObject = new JSONObject();
        for (String s : param.split("&")) {
            String[] split = s.split("=");
            jsonObject.put(split[0], split[1]);
        }
        return jsonObject;
    }
}
