package com.msl.common.open.core;

import com.alibaba.fastjson.JSON;
import com.msl.common.base.Optional;
import com.msl.common.enums.ResultCodeEnum;
import com.msl.common.exception.ServiceSystemException;
import com.msl.common.open.OpenDto;
import com.msl.common.open.Process;
import com.msl.common.open.core.enums.OpenResultEnum;
import com.msl.common.open.core.processor.ProcessorRegistry;
import com.msl.common.result.Result;
import com.msl.common.utils.EncryptUtil;
import com.msl.common.utils.MslCollectionUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.FileCopyUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @author wanglq
 * Date 2024/4/30
 * Time 17:13
 */
@Slf4j
public class OpenEncrypt {
    private final NamespacedEncryptAppFactory appFactory;
    private final BusinessRegistry businessRegistry;
    private final ProcessorRegistry processorRegistry;

    public OpenEncrypt(NamespacedEncryptAppFactory appFactory, BusinessRegistry businessRegistry, ProcessorRegistry processorRegistry) {
        this.appFactory = appFactory;
        this.businessRegistry = businessRegistry;
        this.processorRegistry = processorRegistry;
    }

    /**
     * 处理加密请求
     *
     * @param request
     * @return
     */
    @SneakyThrows
    public OpenDto doWithEncrypt(HttpServletRequest request) {
        // 从body里面取数据
        String body = FileCopyUtils.copyToString(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));

        OpenDto openDto = JSON.parseObject(body, OpenDto.class);
        log.info("OpenEncrypt接收到的加密参数-->{}", JSON.toJSONString(openDto));

        EncryptApp app = appFactory.getByAppId(openDto.getNamespace(), openDto.getAppId())
                .orElseThrow(OpenResultEnum.APP_NOT_EXISTS);

        try {
            // 验证签名
            if (!Objects.equals(openDto.getSign(), EncryptUtil.sign(app.getAppId(), app.getAppKey(), openDto.getData(), openDto.getTimestamp()))) {
                return back(Result.fail(OpenResultEnum.SIGN_ERROR), app);
            }

            // 验证请求时间戳是否超过10分钟
            long currentTimeMillis = System.currentTimeMillis();
            long diff = Math.abs(currentTimeMillis - openDto.getTimestamp());
            if (diff > 600000) {
                return back(Result.fail(OpenResultEnum.TIMESTAMP_ERROR), app);
            }

            // 解密后参数
            String decrypt = EncryptUtil.decrypt(openDto.getData(), app.getAppKey());
            log.info("OpenEncrypt接收到解密后的参数-->{}", decrypt);

            //获取业务处理器
            Optional<BusinessItem<?>> businessItemOptional = businessRegistry.getBusiness(openDto.getNamespace(), openDto.getOperate());
            if (businessItemOptional.isEmpty()) {
                log.info("不支持的操作类型-->{}:{}", openDto.getNamespace(), openDto.getOperate());
                return back(Result.fail("不支持的操作类型"), app);
            }
            BusinessItem<?> business = businessItemOptional.get();

            //转换数据
            Class<? extends Process<?>> clazz = business.getClazz();
            Process<?> process = JSON.parseObject(decrypt, clazz);

            //创建上下文
            ProcessContext context = new ProcessContext()
                    .setApp(app)
                    .setProcess(process);

            //流程链处理
            processorRegistry.doProcess(context);

            //业务处理
            return back(Result.ok(business.doBusiness(app, process)), app);
        } catch (ServiceSystemException sse) {
            log.warn("OpenEncrypt请求处理失败: {}", sse.getDetail());
            return back(Result.fail(sse.getResultEnum(), sse.getDetail()), app);
        } catch (ConstraintViolationException e) {
            StringBuilder sb = new StringBuilder();
            Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
            for (ConstraintViolation<?> violation : violations) {
                sb.append(violation.getMessage()).append(",");
            }
            String errMsg = sb.substring(0, sb.length() - 1);
            log.warn("OpenEncrypt请求参数校验失败", e);
            return back(Result.fail(ResultCodeEnum.ILLEGAL_PARAMETER, errMsg), app);
        } catch (Exception e) {
            if (e instanceof BindException) {
                BindException be = (BindException) e;
                return back(Result.fail(ResultCodeEnum.ILLEGAL_PARAMETER, exceptionMsg(be.getFieldErrors())), app);
            } else if (e instanceof MethodArgumentNotValidException) {
                MethodArgumentNotValidException me = (MethodArgumentNotValidException) e;
                return back(Result.fail(ResultCodeEnum.ILLEGAL_PARAMETER, exceptionMsg(me.getBindingResult().getFieldErrors())), app);
            }
            log.error("OpenEncrypt请求处理失败", e);
            return back(Result.fail(), app);
        }
    }

    public static void main(String[] args) {
        String s = "{\"appId\":\"1121180185901704645\",\"data\":\"vrb1k2P8wLGVUN+B2J16TcAzdT/06Mkxrtk0p3jeqA/CUp03q/SdK4mgInzcXPClCWkB0AZIAL2H2mWVz7CX9JB9LULLvLC4zNrRf7L1uXLnlHmOacLGoXHMy5U3HanCPUIVOpYRIkzIddJVS3wCHoUi1PTxKAAY+QjiaqIhptUNSLnP07MUj37NYa+80BPlhNZ9dMrSZiPsZH8cr4VRQw==\",\"namespace\":\"trade-open\",\"operate\":\"trader.auth.apply\",\"sign\":\"48DF81292A0EC600EE8DF3553D1B7613\",\"timestamp\":1716378281402}";
        OpenDto openDto = JSON.parseObject(s, OpenDto.class);
        System.out.println(openDto);
    }

    private String exceptionMsg(List<FieldError> fieldErrors) {
        return MslCollectionUtil.join(fieldErrors, FieldError::getDefaultMessage, ",").orNull();
    }

    private OpenDto back(Result<?> res, EncryptApp app) {
        try {
            return OpenDto.buildEncrypt(app.getAppId(), JSON.toJSONString(res), app.getAppKey(), System.currentTimeMillis());
        } catch (Exception e) {
            log.error("构造返回消息异常", e);
            throw new RuntimeException(e);
        }
    }
}
