package com.izouma.nineth.service;

import cn.com.sandpay.cashier.sdk.*;
import com.alibaba.fastjson.JSONObject;
import com.izouma.nineth.config.Constants;
import com.izouma.nineth.config.GeneralProperties;
import com.izouma.nineth.config.RedisKeys;
import com.izouma.nineth.config.SandPayProperties;
import com.izouma.nineth.dto.PayQuery;
import com.izouma.nineth.enums.PayStatus;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.repo.AuctionOrderRepo;
import com.izouma.nineth.repo.GiftOrderRepo;
import com.izouma.nineth.repo.MintOrderRepo;
import com.izouma.nineth.repo.OrderRepo;
import com.izouma.nineth.utils.DateTimeUtils;
import com.izouma.nineth.utils.SnowflakeIdWorker;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Service
@AllArgsConstructor
@Slf4j
public class SandPayService {
    private final OrderRepo                     orderRepo;
    private final GiftOrderRepo                 giftOrderRepo;
    private final SandPayProperties             sandPayProperties;
    private final MintOrderRepo                 mintOrderRepo;
    private final SnowflakeIdWorker             snowflakeIdWorker;
    private final GeneralProperties             generalProperties;
    private final AuctionOrderRepo              auctionOrderRepo;
    private final RedisTemplate<String, Object> redisTemplate;

    public static String paddingOrderId(String orderId) {
        if (orderId != null && orderId.length() < 12) {
            StringBuilder orderIdBuilder = new StringBuilder(orderId);
            for (int i = orderIdBuilder.length(); i < 12; i++) {
                orderIdBuilder.insert(0, "0");
            }
            orderId = orderIdBuilder.toString();
        }
        return orderId;
    }

    public static String getReqTime() {
        return DateTimeUtils.format(LocalDateTime.now(), "yyyyMMddHHmmss");
    }

    public static String getTimeout(int seconds) {
        return DateTimeUtils.format(LocalDateTime.now().plusSeconds(seconds), "yyyyMMddHHmmss");
    }

    public static String getTimeout(LocalDateTime createTime, int seconds) {
        return DateTimeUtils.format(Optional.ofNullable(createTime).orElse(LocalDateTime.now())
                .plusSeconds(seconds), "yyyyMMddHHmmss");
    }

    public static String convertAmount(BigDecimal amount) {
        DecimalFormat df = new DecimalFormat("000000000000", DecimalFormatSymbols.getInstance(Locale.US));
        return df.format(amount.multiply(new BigDecimal("100")));
    }

    public static JSONObject requestServer(JSONObject header, JSONObject body, String reqAddr) {

        Map<String, String> reqMap = new HashMap<String, String>();
        JSONObject reqJson = new JSONObject();
        reqJson.put("head", header);
        reqJson.put("body", body);
        String reqStr = reqJson.toJSONString();
        String reqSign;
        // 签名
        try {
            reqSign = new String(Base64.encodeBase64(CryptoUtil.digitalSign(reqStr.getBytes(StandardCharsets.UTF_8),
                    CertUtil.getPrivateKey(), "SHA1WithRSA")));
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
        //整体报文格式
        reqMap.put("charset", "UTF-8");
        reqMap.put("data", reqStr);
        reqMap.put("signType", "01");
        reqMap.put("sign", reqSign);
        reqMap.put("extend", "");

        String result;
        try {
            log.info("请求报文：\n{}", JSONObject.toJSONString(reqJson, true));
            result = HttpClient.doPost(reqAddr, reqMap, 30000, 30000);
            result = URLDecoder.decode(result, StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error(e.getMessage());
            return null;
        }

        Map<String, String> respMap = SDKUtil.convertResultStringToMap(result);
        String respData = respMap.get("data");
        String respSign = respMap.get("sign");

        // 验证签名
        boolean valid;
        try {
            valid = CryptoUtil.verifyDigitalSign(respData.getBytes(StandardCharsets.UTF_8),
                    Base64.decodeBase64(respSign), CertUtil.getPublicKey(), "SHA1WithRSA");
            if (!valid) {
                log.error("verify sign fail.");
                return null;
            }
            log.info("verify sign success");
            JSONObject respJson = JSONObject.parseObject(respData);
            if (respJson != null) {
                log.info("响应码：[{}]", respJson.getJSONObject("head").getString("respCode"));
                log.info("响应描述：[{}]", respJson.getJSONObject("head").getString("respMsg"));
                log.info("响应报文：\n{}", JSONObject.toJSONString(respJson, true));
            } else {
                log.error("服务器请求异常！！！");
            }
            return respJson;

        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
    }

    public String pay(String orderId, String subject, BigDecimal amount, LocalDateTime expireAt, String type) {
        String pOrderId = paddingOrderId(orderId);

        JSONObject extend = new JSONObject();
        extend.put("type", type);
        extend.put("orderId", pOrderId);
        extend.put("id", pOrderId);

        JSONObject header = new JSONObject();
        header.put("version", "1.0");                      //版本号
        header.put("method", "sandpay.trade.precreate");   //接口名称:统一下单并支付
        header.put("productId", "00000006");               //产品编码
        header.put("mid", sandPayProperties.getMid());     //商户号
        header.put("accessType", "1");                     //接入类型设置为普通商户接入
        header.put("channelType", "07");                   //渠道类型：07-互联网   08-移动端
        header.put("reqTime", getReqTime());               //请求时间

        JSONObject body = new JSONObject();
        body.put("payTool", "0401");                                  //支付工具: 固定填写0401
        body.put("orderCode", pOrderId);                              //商户订单号
        body.put("totalAmount", convertAmount(amount));               //订单金额 12位长度，精确到分
        //body.put("limitPay", "5");                                  //限定支付方式 送1-限定不能使用贷记卡	送4-限定不能使用花呗	送5-限定不能使用贷记卡+花呗
        body.put("subject", subject);                                 //订单标题
        body.put("body", subject);                                    //订单描述
        body.put("txnTimeOut", getTimeout(expireAt, 0));      //订单超时时间
        body.put("notifyUrl", sandPayProperties.getNotifyUrl());      //异步通知地址
        body.put("bizExtendParams", "");                              //业务扩展参数
        body.put("merchExtendParams", "");                            //商户扩展参数
        body.put("extend", extend.toJSONString());                    //扩展域

        JSONObject res = requestServer(header, body, "https://cashier.sandpay.com.cn/qr/api/order/create");

        String respCode = res.getJSONObject("head").getString("respCode");
        if ("000000".equals(respCode)) {
            redisTemplate.opsForValue()
                    .set(RedisKeys.PAY_TMP + orderId, Constants.PayChannel.SAND, 1, TimeUnit.DAYS);
            return "alipays://platformapi/startapp?saId=10000007&qrcode="
                    + res.getJSONObject("body").getString("qrCode");
        }
        String msg = res.getJSONObject("head").getString("respMsg");
        throw new BusinessException(Constants.PAY_ERR_MSG, msg);
    }

    public String payQuick(String orderId, String subject, BigDecimal amount, LocalDateTime expireAt,
                           String type, String returnUrl) {
        String pOrderId = paddingOrderId(orderId);

        JSONObject extend = new JSONObject();
        extend.put("type", type);
        extend.put("orderId", pOrderId);
        extend.put("id", pOrderId);

        JSONObject header = new JSONObject();
        header.put("version", "1.0");                         //版本号
        header.put("method", "sandpay.trade.pay");            //接口名称:统一下单
        header.put("mid", sandPayProperties.getMid());        //商户号
        header.put("accessType", "1");                        //接入类型设置为平台商户接入		//接入类型设置为普通商户接入
        header.put("channelType", "07");                      //渠道类型：07-互联网   08-移动端
        header.put("reqTime", getReqTime());                  //请求时间
        header.put("productId", "00000008");                  //产品编码

        JSONObject body = new JSONObject();
        body.put("orderCode", pOrderId);                                          //商户订单号
        body.put("totalAmount", convertAmount(amount));                           //订单金额
        body.put("subject", subject);                                             //订单标题
        body.put("body", subject);                                                //订单描述
        body.put("txnTimeOut", getTimeout(expireAt, 0));                  //订单超时时间
        body.put("clientIp", "192.168.22.55");                                    //客户端IP
        body.put("limitPay", "");                                                 //限定支付方式	送1-限定不能使用贷记卡送	4-限定不能使用花呗	送5-限定不能使用贷记卡+花呗
        body.put("notifyUrl", sandPayProperties.getNotifyUrl());                  //异步通知地址
        body.put("frontUrl", returnUrl);                                          //前台通知地址
        body.put("storeId", "");                                                  //商户门店编号
        body.put("terminalId", "");                                               //商户终端编号
        body.put("operatorId", "");                                               //操作员编号
        body.put("clearCycle", "");                                               //清算模式
        body.put("royaltyInfo", "");                                              //分账信息
        body.put("riskRateInfo", "");                                             //风控信息域
        body.put("bizExtendParams", "");                                          //业务扩展参数
        body.put("merchExtendParams", "");                                        //商户扩展参数
        body.put("extend", extend.toJSONString());                                //扩展域
        body.put("payMode", "sand_h5");                                           //支付模式

        JSONObject res = requestServer(header, body, "https://cashier.sandpay.com.cn/gateway/api/order/pay");
        String respCode = res.getJSONObject("head").getString("respCode");
        if ("000000".equals(respCode)) {
            redisTemplate.opsForValue()
                    .set(RedisKeys.PAY_TMP + orderId, Constants.PayChannel.SAND, 1, TimeUnit.DAYS);
            return res.getJSONObject("body").getString("credential");
        }
        String msg = res.getJSONObject("head").getString("respMsg");
        throw new BusinessException(Constants.PAY_ERR_MSG, msg);
    }

    public JSONObject query(String orderId) {
        orderId = paddingOrderId(orderId);
        JSONObject header = new JSONObject();
        header.put("version", "1.0");                     //版本号
        header.put("method", "sandpay.trade.query");      //接口名称:订单查询
        header.put("productId", "00000006");              //产品编码
        header.put("mid", sandPayProperties.getMid());    //商户号
        header.put("accessType", "1");                    //接入类型设置为普通商户接入
        header.put("channelType", "07");                  //渠道类型：07-互联网   08-移动端
        header.put("reqTime", getReqTime());              //请求时间

        JSONObject body = new JSONObject();
        body.put("orderCode", orderId);
        body.put("extend", "");                            //扩展域

        return requestServer(header, body, "https://cashier.sandpay.com.cn/qr/api/order/query");
    }

    public JSONObject refund(String orderId, BigDecimal amount) {
        orderId = paddingOrderId(orderId);
        JSONObject header = new JSONObject();
        header.put("version", "1.0");                           //版本号
        header.put("method", "sandpay.trade.refund");           //接口名称:退货
        header.put("productId", "00000006");                    //产品编码
        header.put("mid", sandPayProperties.getMid());          //商户号
        header.put("accessType", "1");                          //接入类型设置为普通商户接入
        header.put("channelType", "07");                        //渠道类型：07-互联网   08-移动端
        header.put("reqTime", getReqTime());                    //请求时间

        JSONObject body = new JSONObject();
        body.put("orderCode", snowflakeIdWorker.nextId());        //商户订单号
        body.put("oriOrderCode", paddingOrderId(orderId));        //原交易订单号
        body.put("refundAmount", convertAmount(amount));          //退货金额
        body.put("refundReason", "退款");                         //退货原因
        body.put("notifyUrl", sandPayProperties.getNotifyUrl());  //异步通知地址
        body.put("extend", "");

        return requestServer(header, body, "https://cashier.sandpay.com.cn/qr/api/order/refund");
    }

    public JSONObject transfer(String id, String name, String bank, BigDecimal amount) {
        JSONObject request = new JSONObject();
        DecimalFormat df = new DecimalFormat("000000000000", DecimalFormatSymbols.getInstance(Locale.US));
        request.put("version", "01");                          //版本号
        request.put("productId", "00000004");                  //产品ID
        request.put("tranTime", getReqTime());                 //交易时间
        request.put("orderCode", id);  //订单号
        request.put("timeOut", getTimeout(180));       //订单超时时间
        request.put("tranAmt", df.format(amount.multiply(new BigDecimal("100"))));      //金额
        request.put("currencyCode", "156");                    //币种
        request.put("accAttr", "0");                           //账户属性     0-对私   1-对公
        request.put("accType", "4");                           //账号类型      3-公司账户  4-银行卡
        request.put("accNo", bank);                            //收款人账户号
        request.put("accName", name);                          //收款人账户名
        request.put("provNo", "");                             //收款人开户省份编码
        request.put("cityNo", "");                             //收款人开会城市编码
        request.put("bankName", "");                           //收款账户开户行名称
        request.put("bankType", "");                           //收款人账户联行号
        request.put("remark", "消费");                          //摘要
        request.put("payMode", "");                            //付款模式
        request.put("channelType", "");                        //渠道类型
        request.put("extendParams", "");                       //业务扩展参数
        request.put("reqReserved", "");                        //请求方保留域
        request.put("extend", "");                             //扩展域
        request.put("phone", "");                              //手机号

        String reqData = request.toJSONString();
        log.info("请求数据：{}", reqData);

        try {

            String aesKey = RandomStringGenerator.getRandomStringByLength(16);
            byte[] aesKeyBytes = aesKey.getBytes(StandardCharsets.UTF_8);

            byte[] plainBytes = reqData.getBytes(StandardCharsets.UTF_8);
            String encryptData = new String(Base64.encodeBase64(
                    CryptoUtil.AESEncrypt(plainBytes, aesKeyBytes, "AES",
                            "AES/ECB/PKCS5Padding", null)), StandardCharsets.UTF_8);

            String sign = new String(Base64.encodeBase64(
                    CryptoUtil.digitalSign(plainBytes, CertUtil.getPrivateKey(),
                            "SHA1WithRSA")), StandardCharsets.UTF_8);

            String encryptKey = new String(Base64.encodeBase64(
                    CryptoUtil.RSAEncrypt(aesKeyBytes, CertUtil.getPublicKey(), 2048, 11,
                            "RSA/ECB/PKCS1Padding")), StandardCharsets.UTF_8);


            Map<String, String> reqMap = new HashMap<String, String>();
            //整体报文格式
            reqMap.put("transCode", "RTPM"); // 交易码
            reqMap.put("accessType", "0"); // 接入类型
            reqMap.put("merId", sandPayProperties.getMid()); // 合作商户ID	杉德系统分配，唯一标识
            reqMap.put("encryptKey", encryptKey); // 加密后的AES秘钥
            reqMap.put("encryptData", encryptData); // 加密后的请求/应答报文
            reqMap.put("sign", sign); // 签名
            reqMap.put("extend", ""); // 扩展域

            String result;
            try {
                log.info("请求报文：{}", reqMap);
                result = HttpClient.doPost("https://caspay.sandpay.com.cn/agent-main/openapi/agentpay",
                        reqMap, 300000, 300000);
                result = URLDecoder.decode(result, StandardCharsets.UTF_8);
            } catch (IOException e) {
                log.error(e.getMessage());
                return null;
            }

            log.info("响应报文：{}", result);
            Map<String, String> responseMap = SDKUtil.convertResultStringToMap(result);

            String retEncryptKey = responseMap.get("encryptKey");
            String retEncryptData = responseMap.get("encryptData");
            String retSign = responseMap.get("sign");

            log.debug("retEncryptKey:[{}]", retEncryptKey);
            log.debug("retEncryptData:[{}]", retEncryptData);
            log.debug("retSign:[{}]", retSign);

            byte[] decodeBase64KeyBytes = Base64.decodeBase64(retEncryptKey
                    .getBytes(StandardCharsets.UTF_8));

            byte[] merchantAESKeyBytes = CryptoUtil.RSADecrypt(
                    decodeBase64KeyBytes, CertUtil.getPrivateKey(), 2048, 11,
                    "RSA/ECB/PKCS1Padding");

            byte[] decodeBase64DataBytes = Base64.decodeBase64(retEncryptData.getBytes(StandardCharsets.UTF_8));

            byte[] respDataBytes = CryptoUtil.AESDecrypt(decodeBase64DataBytes,
                    merchantAESKeyBytes, "AES", "AES/ECB/PKCS5Padding", null);

            String respData = new String(respDataBytes, StandardCharsets.UTF_8);
            log.info("retData:[" + respData + "]");
            System.out.println("响应data数据：" + respData);

            byte[] signBytes = Base64.decodeBase64(retSign.getBytes(StandardCharsets.UTF_8));

            boolean isValid = CryptoUtil.verifyDigitalSign(respDataBytes, signBytes,
                    CertUtil.getPublicKey(), "SHA1WithRSA");

            if (!isValid) {
                log.error("verify sign fail.");
                return null;
            }
            log.info("verify sign success");
            System.out.println("verify sign success");

            JSONObject respJson = JSONObject.parseObject(respData);
            return respJson;

        } catch (Exception e) {

            log.error(e.getMessage());
            return null;
        }
    }

    public JSONObject queryTransfer(String tranTime, String orderId) {
        JSONObject request = new JSONObject();
        request.put("version", "01");          // 版本号
        request.put("productId", "00000004");  // 产品ID
        request.put("tranTime", tranTime);     // 查询订单的交易时间
        request.put("orderCode", orderId);     // 要查询的订单号

        String reqData = request.toJSONString();
        log.info("请求数据：{}", reqData);

        try {

            String aesKey = RandomStringGenerator.getRandomStringByLength(16);
            byte[] aesKeyBytes = aesKey.getBytes(StandardCharsets.UTF_8);

            byte[] plainBytes = reqData.getBytes(StandardCharsets.UTF_8);
            String encryptData = new String(Base64.encodeBase64(
                    CryptoUtil.AESEncrypt(plainBytes, aesKeyBytes, "AES",
                            "AES/ECB/PKCS5Padding", null)), StandardCharsets.UTF_8);

            String sign = new String(Base64.encodeBase64(
                    CryptoUtil.digitalSign(plainBytes, CertUtil.getPrivateKey(),
                            "SHA1WithRSA")), StandardCharsets.UTF_8);

            String encryptKey = new String(Base64.encodeBase64(
                    CryptoUtil.RSAEncrypt(aesKeyBytes, CertUtil.getPublicKey(), 2048, 11,
                            "RSA/ECB/PKCS1Padding")), StandardCharsets.UTF_8);


            Map<String, String> reqMap = new HashMap<String, String>();
            //整体报文格式
            reqMap.put("transCode", "ODQU"); // 交易码
            reqMap.put("accessType", "0"); // 接入类型
            reqMap.put("merId", sandPayProperties.getMid()); // 合作商户ID	杉德系统分配，唯一标识
            reqMap.put("plId", null);
            reqMap.put("encryptKey", encryptKey); // 加密后的AES秘钥
            reqMap.put("encryptData", encryptData); // 加密后的请求/应答报文
            reqMap.put("sign", sign); // 签名
            reqMap.put("extend", ""); // 扩展域

            String result;
            try {
                log.info("请求报文：{}", reqMap);
                result = HttpClient.doPost("https://caspay.sandpay.com.cn/agent-main/openapi/queryOrder",
                        reqMap, 300000, 300000);
                result = URLDecoder.decode(result, StandardCharsets.UTF_8);
            } catch (IOException e) {
                log.error(e.getMessage());
                return null;
            }

            log.info("响应报文：{}", result);
            Map<String, String> responseMap = SDKUtil.convertResultStringToMap(result);

            String retEncryptKey = responseMap.get("encryptKey");
            String retEncryptData = responseMap.get("encryptData");
            String retSign = responseMap.get("sign");

            log.debug("retEncryptKey:[{}]", retEncryptKey);
            log.debug("retEncryptData:[{}]", retEncryptData);
            log.debug("retSign:[{}]", retSign);

            byte[] decodeBase64KeyBytes = Base64.decodeBase64(retEncryptKey
                    .getBytes(StandardCharsets.UTF_8));

            byte[] merchantAESKeyBytes = CryptoUtil.RSADecrypt(
                    decodeBase64KeyBytes, CertUtil.getPrivateKey(), 2048, 11,
                    "RSA/ECB/PKCS1Padding");

            byte[] decodeBase64DataBytes = Base64.decodeBase64(retEncryptData.getBytes(StandardCharsets.UTF_8));

            byte[] respDataBytes = CryptoUtil.AESDecrypt(decodeBase64DataBytes,
                    merchantAESKeyBytes, "AES", "AES/ECB/PKCS5Padding", null);

            String respData = new String(respDataBytes, StandardCharsets.UTF_8);
            log.info("retData:[" + respData + "]");
            System.out.println("响应data数据：" + respData);

            byte[] signBytes = Base64.decodeBase64(retSign.getBytes(StandardCharsets.UTF_8));

            boolean isValid = CryptoUtil.verifyDigitalSign(respDataBytes, signBytes,
                    CertUtil.getPublicKey(), "SHA1WithRSA");

            if (!isValid) {
                log.error("verify sign fail.");
                return null;
            }
            log.info("verify sign success");
            System.out.println("verify sign success");

            JSONObject respJson = JSONObject.parseObject(respData);
            return respJson;

        } catch (Exception e) {

            log.error(e.getMessage());
            return null;
        }
    }

    public PayQuery payQuery(String orderId) {
        JSONObject res = query(orderId);
        PayQuery query = new PayQuery(Constants.PayChannel.SAND);
        String respCode = res.getJSONObject("head").getString("respCode");

        if ("000000".equals(respCode)) {
            query.setExist(true);
            JSONObject body = res.getJSONObject("body");
            query.setMsg(body.getString("orderMsg") + " " + body.getString("oriRespMsg"));
            query.setAmount(new BigDecimal(body.getString("totalAmount"))
                    .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP));
            query.setTransactionId(body.getString("payOrderCode"));
            if (StringUtils.isNotEmpty(body.getString("payTime"))) {
                query.setPayTime(LocalDateTime.parse(body.getString("payTime"), DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
            }
            if (StringUtils.isNotEmpty(body.getString("oriOrderCode"))) {
                query.setOrderId(body.getString("oriOrderCode"));
            }
            switch (body.getString("orderStatus")) {
                case "00":
                    query.setStatus(PayStatus.SUCCESS);
                    break;
                case "01":
                    query.setStatus(PayStatus.PENDING);
                    break;
                case "02":
                    query.setStatus(PayStatus.FAIL);
                    break;
                case "03":
                    query.setStatus(PayStatus.CANCEL);
                    break;
                case "04":
                    query.setStatus(PayStatus.REFUNDED);
                    break;
                case "05":
                    query.setStatus(PayStatus.REFUNDING);
                    break;
            }
        } else {
            query.setExist(false);
            query.setMsg(res.getString("msg"));
        }
        return query;
    }
}
