package com.zanxiang.game.module.sdk.service.pay;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.zanxiang.game.module.base.pojo.enums.DeleteEnum;
import com.zanxiang.game.module.base.pojo.enums.HttpStatusEnum;
import com.zanxiang.game.module.base.pojo.enums.PayDeviceEnum;
import com.zanxiang.game.module.mybatis.entity.Order;
import com.zanxiang.game.module.mybatis.entity.OrderPayParam;
import com.zanxiang.game.module.sdk.constant.WxPayConstants;
import com.zanxiang.game.module.sdk.enums.OrderStateEnum;
import com.zanxiang.game.module.sdk.pojo.dto.*;
import com.zanxiang.game.module.sdk.service.*;
import com.zanxiang.game.module.sdk.service.api.WxApiService;
import com.zanxiang.game.module.sdk.util.HttpUtil;
import com.zanxiang.game.module.sdk.util.WxPayUtil;
import com.zanxiang.game.module.sdk.util.XmlUtil;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.NumberUtil;
import com.zanxiang.module.util.exception.BaseException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author : lingfeng
 * @time : 2022-09-28
 * @description : 支付超类
 */
@Slf4j
@Service
public class WxPayService extends PayBaseService {

    /**
     * pc二维码支付
     */
    private static final String WX_PAY_NATIVE = "NATIVE";

    /**
     * h5支付
     */
    private static final String WX_PAY_MWEB = "MWEB";

    /**
     * 小程序支付
     */
    private static final String WX_PAY_JSAPI = "JSAPI";

    /**
     * 成功
     */
    private static final String SUCCESS = "SUCCESS";

    /**
     * 服务器域名
     */
    @Value("${server.domain}")
    private String serverUrl;

    /**
     * 微信回调接口
     */
    @Value("${payConfig.wxPay.notifyUrl}")
    private String notifyUrl;

    @Autowired
    private IOrderPayParamService orderPayParamService;

    @Autowired
    private WxApiService wxApiService;

    @Autowired
    private IPayMerchantService payMerchantService;

    @Autowired
    private IPayApplicationService payApplicationService;

    @Autowired
    private IPayBoxService payBoxService;

    @Autowired
    private IOrderService orderService;

    @Override
    public Map<String, Object> create(ProductPayParamDTO product, GamePayWayDTO gamePayWayDTO) {
        //初始化配置
        WxPayConfigDTO config = this.configInit(gamePayWayDTO);
        //支付方式
        int payDevice = product.getPayDevice().intValue();
        //不同的支付途径
        Map<String, Object> resultMap;
        switch (payDevice) {
            case 1:
                //PC
                resultMap = this.pcPay(product, config);
                break;
            case 2:
                //H5
                resultMap = this.h5Pay(product, config);
                break;
            case 4:
            case 6:
            case 7:
                //小程序
                resultMap = this.miniAppPay(product, config);
                break;
            default:
                throw new RuntimeException("未知支付方式");
        }
        //订单支付添加过期缓存
        this.orderExpire(product.getOutTradeNo());
        //更新订单商户信息
        orderService.update(new LambdaUpdateWrapper<Order>()
                .set(Order::getMerchantNo, config.getMchId())
                .set(Order::getStatus, OrderStateEnum.WAIT_PAY.getCode())
                .eq(Order::getOrderId, product.getOutTradeNo()));
        //返回支付参数
        log.error("返回支付参数 resultMap : {}", JsonUtil.toString(resultMap));
        return resultMap;
    }

    @Override
    public String notify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 读取参数
        InputStream inputStream = request.getInputStream();
        String requestStr = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
        // 解析xml成map
        Map<String, String> packageParams = XmlUtil.xmlToMap(requestStr);
        log.error("微信支付回调参数, packageParams : {}", JsonUtil.toString(packageParams));
        //获取订单信息
        String attachStr = packageParams.get("attach");
        ProductPayAttachParamDTO attachBO = JsonUtil.toObj(attachStr, ProductPayAttachParamDTO.class);
        log.error("微信支付回调参数, attachBO : {}", JsonUtil.toString(attachBO));
        if (attachBO == null) {
            log.info("回调参数中attach值为空");
            return null;
        }
        //查询订单
        PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(packageParams.get("out_trade_no"));
        if (platformOrderDTO == null) {
            log.info("回调参数中订单id无法查询到订单信息");
            return null;
        }
        WxPayConfigDTO config = this.notifyConfigInit(platformOrderDTO, gamePayWayService.getById(attachBO.getGamePayWayId()));
        // 账号信息
        String key = config.getApiKey();
        // 判断签名是否正确
        String mySign = WxPayUtil.generateSignature(packageParams, key);
        if (!Objects.equals(mySign, packageParams.get("sign"))) {
            log.error("通知签名验证失败, muSign : {}, sign : {}", mySign, packageParams.get("sign"));
            return null;
        }
        log.info("微信支付成功回调");
        // 处理业务开始
        Map<String, String> xmlMap = new HashMap<>();
        if (SUCCESS.equalsIgnoreCase(packageParams.get("result_code"))) {
            String orderNo = packageParams.get("out_trade_no");
            String totalFee = String.valueOf(Float.parseFloat(packageParams.get("total_fee")) / 100);
            log.info("微信订单号回调成功, orderId : {}", orderNo);
            if (paySuccess(orderNo, totalFee, packageParams.get("transaction_id"))) {
                // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了
                xmlMap.put("return_code", "SUCCESS");
                xmlMap.put("return_msg", "OK");
            }
        } else {
            log.info("支付失败,错误信息：{}", packageParams.get("err_code"));
            xmlMap.put("return_code", "FAIL");
            xmlMap.put("return_msg", "报文为空");
        }
        // 处理业务完毕
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        log.info("返回给腾讯的回调结果, ss : {}", XmlUtil.mapToXml(xmlMap));
        out.write(XmlUtil.mapToXml(xmlMap).getBytes());
        out.flush();
        out.close();
        return HttpStatusEnum.SUCCESS.getMsg();
    }

    private Map<String, Object> pcPay(ProductPayParamDTO product, WxPayConfigDTO config) {
        //下单
        Map<String, String> successMap = this.unifiedOrder(product, WX_PAY_NATIVE, null, config);
        //获取二维码链接
        String urlCode = successMap.get("code_url");
        //判断是否获取到支付链接
        if (Strings.isBlank(urlCode)) {
            log.error("微信PC支付链接urlCode为空, product：{}", JsonUtil.toString(product));
            throw new BaseException("微信PC支付链接urlCode为空");
        }
        Map<String, Object> payParamMap = new HashMap<>(3);
        payParamMap.put("deviceType", product.getPayDevice());
        payParamMap.put("data", urlCode);
        payParamMap.put("orderId", product.getOutTradeNo());
        return payParamMap;
    }

    private Map<String, Object> h5Pay(ProductPayParamDTO product, WxPayConfigDTO config) {
        //下单
        Map<String, String> successMap = this.unifiedOrder(product, WX_PAY_MWEB, null, config);
        //获取h5支付链接
        String urlCode = successMap.get("mweb_url");
        if (Strings.isBlank(urlCode)) {
            log.error("微信h5支付链接urlCode为空, product：{}", JsonUtil.toString(product));
            throw new BaseException("微信h5支付链接urlCode为空");
        }
        //转换deepLink
        String deepLink;
        try {
            deepLink = WxPayUtil.readUrl(urlCode, serverUrl);
        } catch (Exception e) {
            log.error("微信h5支付urlCode转deepLink异常, urlCode：{}", urlCode);
            throw new BaseException("微信h5支付urlCode转deepLink异常");
        }
        Map<String, Object> payParamMap = new HashMap<>(3);
        payParamMap.put("deviceType", product.getPayDevice());
        payParamMap.put("data", deepLink);
        payParamMap.put("orderId", product.getOutTradeNo());
        return payParamMap;
    }

    private Map<String, Object> miniAppPay(ProductPayParamDTO product, WxPayConfigDTO config) {
        try {
            //用户openId
            String openId = product.getOpenId();
            //传了code, 自己获取openId
            if (Strings.isBlank(openId) && Strings.isNotBlank(product.getCode())) {
                openId = wxApiService.getOpenIdByType(product.getCode(), config.getAppId(), config.getAppSecret(),
                        config.getAppletType()).get("openid");
            }
            //下单
            Map<String, String> successMap = this.unifiedOrder(product, WX_PAY_JSAPI, openId, config);
            // 支付参数
            String prepayId = successMap.get("prepay_id");
            // 随机字符串
            String nonceStr = WxPayUtil.generateNonceStr();
            // 时间戳转化成字符串，否则前端wx.requestPayment方法会报签名错误
            String createTime = String.valueOf(System.currentTimeMillis() / 1000);
            // 获取统一下单回调结果, 二次签名
            HashMap<String, String> map = new HashMap<>(5);
            map.put("appId", config.getAppId());
            map.put("timeStamp", createTime);
            map.put("nonceStr", nonceStr);
            map.put("package", "prepay_id=" + prepayId);
            map.put("signType", config.getSignType());
            // 再次签名sign，这个签名用于小程序端调用支付方法
            String sign = WxPayUtil.generateSignature(map, config.getApiKey());
            //组装支付参数
            Map<String, Object> payParamMap = new HashMap<>(6);
            payParamMap.put("appId", config.getAppId());
            payParamMap.put("paySign", sign);
            payParamMap.put("signType", config.getSignType());
            payParamMap.put("timeStamp", createTime);
            payParamMap.put("nonceStr", nonceStr);
            payParamMap.put("package", successMap);
            //保存订单支付参数
            orderPayParamService.save(OrderPayParam.builder()
                    .orderId(product.getOutTradeNo())
                    .amount(new BigDecimal(product.getTotalFee()))
                    .appId(config.getAppId())
                    .payParam(JsonUtil.toString(payParamMap))
                    .isDelete(DeleteEnum.NO.getCode())
                    .createTime(LocalDateTime.now())
                    .updateTime(LocalDateTime.now())
                    .build());
            //返回支付参数
            return payParamMap;
        } catch (Exception e) {
            log.error("微信小程序支付通信异常, 订单号：{}, e : {}", product.getOutTradeNo(), e.getMessage());
            throw new BaseException("JSAPI支付通信异常");
        }
    }

    @Override
    public void closeOrder(PlatformOrderDTO platformOrderDTO) {
        //支付配置
        GamePayWayDTO gamePayWayDTO = gamePayWayService.closeOrderGetGamePayWay(platformOrderDTO.getGameId(),
                platformOrderDTO.getPayWayId(), platformOrderDTO.getPayDeviceId());
        //初始化配置
        WxPayConfigDTO config = this.notifyConfigInit(platformOrderDTO, gamePayWayDTO);
        try {
            Map<String, String> paramData = new HashMap<>(6);
            paramData.put("appid", config.getAppId());
            paramData.put("mch_id", config.getMchId());
            paramData.put("out_trade_no", platformOrderDTO.getOrderId());
            paramData.put("nonce_str", WxPayUtil.generateNonceStr());
            paramData.put("sign_type", config.getSignType());
            paramData.put("sign", WxPayUtil.generateSignature(paramData, config.getApiKey()));
            // 取消订单, 获取结果
            String result = HttpUtil.postData(WxPayConstants.CLOSE_ORDER_URL, XmlUtil.mapToXml(paramData));
            Map<String, String> successMap = XmlUtil.xmlToMap(result);
            // 返回状态码
            String returnCode = successMap.get("return_code");
            // 结果状态码
            String resultCode = successMap.get("result_code");
            //成功, 返回结果
            if (SUCCESS.equalsIgnoreCase(returnCode) && returnCode.equals(resultCode)) {
                return;
            }
            log.error("微信支付关闭订单失败, paramData：{}, successMap : {}", JsonUtil.toString(paramData), JsonUtil.toString(successMap));
        } catch (Exception e) {
            log.error("微信支付关闭订单异常, 订单号：{}, e : {}", platformOrderDTO.getOrderId(), e.getMessage());
        }
    }

    private Map<String, String> unifiedOrder(ProductPayParamDTO product, String tradeType, String openId, WxPayConfigDTO config) {
        ProductPayAttachParamDTO attachBO = new ProductPayAttachParamDTO();
        attachBO.setUserId(product.getUserId());
        attachBO.setOrderId(product.getOutTradeNo());
        attachBO.setPayWay(product.getPayWay());
        attachBO.setGamePayWayId(config.getGamePayWayId());
        try {
            Map<String, String> paramData = new HashMap<>(13);
            paramData.put("appid", config.getAppId());
            paramData.put("mch_id", config.getMchId());
            paramData.put("nonce_str", WxPayUtil.generateNonceStr());
            paramData.put("body", product.getSubject());
            paramData.put("out_trade_no", product.getOutTradeNo());
            paramData.put("fee_type", "CNY");
            paramData.put("total_fee", String.valueOf(NumberUtil.multiply100(new BigDecimal(product.getTotalFee())).longValue()));
            paramData.put("spbill_create_ip", product.getSpbillCreateIp());
            paramData.put("notify_url", notifyUrl);
            paramData.put("trade_type", tradeType);
            paramData.put("attach", JsonUtil.toString(attachBO));
            paramData.put("sign_type", config.getSignType());
            //小程序商城下单需要携带openId
            if (Strings.isNotBlank(openId)) {
                paramData.put("openid", openId);
            }
            //接口签名
            String sign = WxPayUtil.generateSignature(paramData, config.getApiKey());
            paramData.put("sign", sign);
            // 下单, 获取结果
            String result = HttpUtil.postData(WxPayConstants.UNIFIED_ORDER_URL, XmlUtil.mapToXml(paramData));
            Map<String, String> successMap = XmlUtil.xmlToMap(result);
            // 返回状态码
            String returnCode = successMap.get("return_code");
            // 结果状态码
            String resultCode = successMap.get("result_code");
            //成功, 返回结果
            if (SUCCESS.equalsIgnoreCase(returnCode) && returnCode.equals(resultCode)) {
                return successMap;
            }
            //下单失败
            log.error("微信支付通信异常, paramData：{}, successMap : {}", JsonUtil.toString(paramData), JsonUtil.toString(successMap));
            throw new BaseException("微信小程序支付下单失败");
        } catch (Exception e) {
            log.error("微信支付通信异常, 订单号：{}, e : {}", product.getOutTradeNo(), e.getMessage());
            this.listenCall(product.getGameId(), "小马支付通道异常");
            throw new BaseException("微信小程序支付下单异常");
        }
    }

    private WxPayConfigDTO configInit(GamePayWayDTO gamePayWayDTO) {
        //商户信息
        PayMerchantDTO payMerchantDTO;
        //支付应用信息
        PayApplicationDTO payApplicationDTO;
        //小程序支付, 客服支付, 公众号支付, 关联了盒子
        if (PayDeviceEnum.getByPayWayIdList().contains(gamePayWayDTO.getPayDeviceId())) {
            payMerchantDTO = payBoxService.getMerchantByBoxId(gamePayWayDTO.getPayBoxId());
            payApplicationDTO = payApplicationService.getPayApplicationByPayBoxId(gamePayWayDTO.getPayBoxId());
        } else {
            payMerchantDTO = payMerchantService.getByMerchantNo(gamePayWayDTO.getMerchantNo());
            payApplicationDTO = payApplicationService.getPayApplicationByAppId(gamePayWayDTO.getAppId());
        }
        //支付配置参数
        WxPayConfigDTO payConfigBO = JsonUtil.toObj(payMerchantDTO.getPayConfig(), WxPayConfigDTO.class);
        payConfigBO.setAppId(payApplicationDTO.getAppId());
        payConfigBO.setAppSecret(payApplicationDTO.getAppSecret());
        payConfigBO.setAppletType(payApplicationDTO.getType());
        payConfigBO.setMachName(payMerchantDTO.getMerchantName());
        payConfigBO.setGamePayWayId(gamePayWayDTO.getId());
        //赋值配置信息
        return payConfigBO;
    }

    private WxPayConfigDTO notifyConfigInit(PlatformOrderDTO platformOrderDTO, GamePayWayDTO gamePayWayDTO) {
        //商户信息
        PayMerchantDTO payMerchantDTO = payMerchantService.getByMerchantNo(platformOrderDTO.getMerchantNo());
        //支付应用信息
        PayApplicationDTO payApplicationDTO;
        //小程序支付, 客服支付, 公众号支付, 关联了盒子
        if (PayDeviceEnum.getByPayWayIdList().contains(gamePayWayDTO.getPayDeviceId())) {
            payApplicationDTO = payApplicationService.getPayApplicationByPayBoxId(gamePayWayDTO.getPayBoxId());
        } else {
            payApplicationDTO = payApplicationService.getPayApplicationByAppId(gamePayWayDTO.getAppId());
        }
        //支付配置参数
        WxPayConfigDTO payConfigBO = JsonUtil.toObj(payMerchantDTO.getPayConfig(), WxPayConfigDTO.class);
        payConfigBO.setAppId(payApplicationDTO.getAppId());
        payConfigBO.setAppSecret(payApplicationDTO.getAppSecret());
        payConfigBO.setAppletType(payApplicationDTO.getType());
        payConfigBO.setGamePayWayId(gamePayWayDTO.getId());
        payConfigBO.setMachName(payMerchantDTO.getMerchantName());
        //赋值配置信息
        return payConfigBO;
    }
}
