package thyyxxk.webserver.service.ca;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.dtflys.forest.Forest;
import com.dtflys.forest.http.ForestRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import thyyxxk.webserver.config.exception.BizException;
import thyyxxk.webserver.config.exception.ExceptionEnum;
import thyyxxk.webserver.constants.YesOrNo;
import thyyxxk.webserver.entity.ResultVo;
import thyyxxk.webserver.entity.ca.CaData;
import thyyxxk.webserver.entity.ca.CaReturn;
import thyyxxk.webserver.entity.ca.CaSendParams;
import thyyxxk.webserver.entity.ca.CaSingBizSnReturnData;
import thyyxxk.webserver.entity.login.UserInfo;
import thyyxxk.webserver.service.redislike.RedisLikeService;
import thyyxxk.webserver.utils.ResultVoUtil;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
public class CaServer {

    private final CaData caData;
    private final String SUCCRSS = "success";
    private final RedisLikeService redisLikeService;

    public CaServer(CaData caData, RedisLikeService redisLikeService) {
        this.caData = caData;
        this.redisLikeService = redisLikeService;
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class Send extends CaSendParams {
        private String id;
        private String msg;
        private String desc;
        private Integer count = 1;
    }

    public List<CaReturn.CaData> sendBatchByCode(Send send) {
        List<CaReturn.CaData> returnValue = new ArrayList<>();
        for (int i = 0; i < send.getCount(); i++) {
            returnValue.add(sendByCode(send));
        }
        return returnValue;
    }

    public CaReturn.CaData sendByCode(Send send) {
        UserInfo userInfoByCode = redisLikeService.getUserInfoByCode(send.getId());
        send.setBizSn(IdUtil.simpleUUID());
        CaReturn push = push(send);
        signatureAuthentication(send, push);
        String timeStamp = generateTimestampByInData(send);
        push.getData().setTimeStamp(timeStamp);
        push.getData().setBizSn(send.getBizSn());
        push.getData().setSignature("http://172.16.32.167:8077/doctorSignatureImage/" + send.getId() + ".png");
        push.getData().setName(userInfoByCode.getName());
        return push.getData();
    }

    /**
     * 推送用户签名
     *
     * @param send
     * @return 返回值
     */
    private CaReturn push(Send send) {
        CaReturn caReturn;
        try {
            CaData.MobileApp app = caData.getMobileApp();
            ForestRequest<?> post = Forest.post(app.getUrl() + "/v1/push/sign");
            send.setAppId(app.getAppId());
            send.setMsg(CaUtils.GetSHA256FormString(send.getMsg()));
            send.setMsgWrapper("0");
            send.setUrl(URLEncoder.encode("http://127.0.0.1/", "UTF-8"));
            send.setMode("redirect");
            send.setSign(CaUtils.getSign(app.getPrivatekey(), send.getMsg()));
            String execute = post.addBody(send).execute(String.class);
            String decode = URLDecoder.decode(execute, "UTF-8");
            log.info("返回数据：{}", decode);
            caReturn = JSONUtil.toBean(decode, CaReturn.class);
            if (!SUCCRSS.equals(caReturn.getRet())) {
                throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名失败。" + caReturn.getMsg());
            }
            if (Integer.valueOf(1).equals(caReturn.getData().getIsTrust())) {
                throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名失败，请用户打开免密签名。");
            }
            if (Integer.valueOf(2).equals(caReturn.getData().getIsTrust())) {
                return caReturn;
            }
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名失败。" + caReturn.getMsg());
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage());
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名失败。" + e.getMessage());
        }
    }

    /**
     * 认证签名
     *
     * @param send
     * @param caReturn
     */
    private void signatureAuthentication(Send send, CaReturn caReturn) {
        CaData.SignAuthentication signData = caData.getSignAuthentication();
        CaWSDL caWSDL = new CaWSDL(signData.getUrl());
        String replace = getVerifySignDataParams(send, caReturn, signData);
        JSONObject rst = caWSDL.invoke("verifySignData", replace);
        log.info("认证签名xml：{}\n认证签名返回值:{}\n", replace, rst.toString());
        Integer code = rst.getByPath("Root.RetCode", Integer.class);
        if (!YesOrNo.YES.getCode().equals(code)) {
            String msg = rst.getByPath("Root.RetMsg", String.class);
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, msg);
        }

    }

    private static @NotNull String getVerifySignDataParams(Send send, CaReturn caReturn, CaData.SignAuthentication signData) {
        String request = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
                "<Root>\n" +
                "\t<AppCode>${AppCode}</AppCode>\n" +
                "\t<AppPWD>${AppPWD}</AppPWD>\n" +
                "\t<Request>\n" +
                "\t\t<Cert>${Cert}</Cert>\n" +
                "\t\t<SignAlg>${SignAlg}</SignAlg>\n" +
                "\t\t<InData>${InData}</InData>\n" +
                "\t\t<SignData>${SignData}</SignData>\n" +
                "\t</Request>\n" +
                "</Root>";

        return request.replace("${AppCode}", signData.getAppCode())
                .replace("${AppPWD}", signData.getAppPwd())
                .replace("${Cert}", caReturn.getData().getCert())
                .replace("${SignAlg}", signData.getSignAig())
                .replace("${InData}", send.getMsg())
                .replace("${SignData}", caReturn.getData().getSignValue());
    }

    /**
     * 认证时间戳并返回
     *
     * @param send 数据
     * @return
     */
    private String generateTimestampByInData(Send send) {
        String data = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
                "<Root>\n" +
                "\t<AppCode>${AppCode}</AppCode>\n" +
                "\t<AppPWD>${AppPWD}</AppPWD>\n" +
                "\t<Request>\n" +
                "\t\t<InData>${InData}</InData>\n" +
                "<HashAlg>${HashAlg}</HashAlg>\n" +
                "<CertReq>${CertReq}</CertReq>\n" +
                "\t</Request>\n" +
                "</Root>";

        CaData.TimestampAuthentication timeData = caData.getTimestampAuthentication();
        String request = data.replace("${AppCode}", timeData.getAppCode())
                .replace("${AppPWD}", timeData.getAppPwd())
                .replace("${InData}", send.getMsg())
                .replace("${HashAlg}", "SHA256")
                .replace("${CertReq}", "true");

        CaWSDL caWSDL = new CaWSDL(timeData.getUrl());
        JSONObject ret = caWSDL.invoke("generateTimestampByInData", request);
        Integer code = ret.getByPath("Root.RetCode", Integer.class);
        log.info("认证时间戳xml：{}\n认证时间戳返回值:{}\n", request, ret);
        if (code != 1) {
            String msg = ret.getByPath("Root.RetMsg", String.class);
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, msg);
        }
        return ret.getByPath("Root.Response.TimeStamp", String.class);
    }

    public ResultVo<CaSingBizSnReturnData> getSignInfoByBizSn(String bizSn) {
        CaData.MobileApp app = caData.getMobileApp();
        ForestRequest<?> post = Forest.post(app.getUrl() + "/v1/sign/info/" + bizSn);
        CaSendParams send = new CaSendParams();
        send.setAppId(app.getAppId());

        String indata = "appId=" + app.getAppId() + "&bizSn=" + bizSn;
        String sign = CaUtils.getSign(app.getPrivatekey(), indata);
        send.setSign(sign);

        String ret = post.addBody(send).execute(String.class);

        CaSingBizSnReturnData execute = JSONUtil.toBean(ret, CaSingBizSnReturnData.class);
        if (SUCCRSS.equals(execute.getRet())) {
            CaSingBizSnReturnData.SingData data = execute.getData();
            try {
                data.setSignData(URLDecoder.decode(data.getSignData(), "UTF-8"));
                data.setCert(URLDecoder.decode(data.getCert(), "UTF-8"));
                data.setSeal(URLDecoder.decode(data.getSeal(), "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error(e.getMessage());
                return ResultVoUtil.success(ExceptionEnum.LOGICAL_ERROR, "解码失败请联系管理员" + bizSn);
            }
            return ResultVoUtil.success(execute);
        }
        return ResultVoUtil.success(ExceptionEnum.LOGICAL_ERROR, execute.getMsg());
    }

}
