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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zanxiang.game.module.base.ServerInfo;
import com.zanxiang.game.module.base.pojo.enums.HttpStatusEnum;
import com.zanxiang.game.module.base.rpc.IPayBoxRpc;
import com.zanxiang.game.module.mybatis.entity.PayApplication;
import com.zanxiang.game.module.mybatis.entity.PayMerchant;
import com.zanxiang.game.module.mybatis.mapper.PayApplicationMapper;
import com.zanxiang.game.module.sdk.constant.RedisKeyConstant;
import com.zanxiang.game.module.sdk.constant.WxPayConstants;
import com.zanxiang.game.module.sdk.pojo.dto.PayApplicationDTO;
import com.zanxiang.game.module.sdk.pojo.dto.PayBoxDTO;
import com.zanxiang.game.module.sdk.pojo.dto.PayMerchantDTO;
import com.zanxiang.game.module.sdk.pojo.dto.PlatformOrderDTO;
import com.zanxiang.game.module.sdk.pojo.param.MiniAppPayParam;
import com.zanxiang.game.module.sdk.service.IOrderService;
import com.zanxiang.game.module.sdk.service.IPayApplicationService;
import com.zanxiang.game.module.sdk.service.IPayBoxService;
import com.zanxiang.game.module.sdk.service.IPayMerchantService;
import com.zanxiang.game.module.sdk.service.api.WxApiService;
import com.zanxiang.game.module.sdk.util.HttpUtil;
import com.zanxiang.game.module.sdk.util.RedisUtil;
import com.zanxiang.game.module.sdk.util.WxPayUtil;
import com.zanxiang.game.module.sdk.util.XmlUtil;
import com.zanxiang.module.util.DateUtil;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.bean.BeanUtil;
import com.zanxiang.module.util.exception.BaseException;
import com.zanxiang.module.util.pojo.ResultVO;
import com.zanxiang.module.web.util.IpUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
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.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 支付商城小程序
 *
 * @author xufeng
 * @date 2022-06-20 15:16
 */
@Slf4j
@Service
public class PayApplicationServiceImpl extends ServiceImpl<PayApplicationMapper, PayApplication> implements IPayApplicationService {

    @DubboReference(providedBy = ServerInfo.SERVER_DUBBO_NAME)
    private IPayBoxRpc payBoxRpc;

    @Autowired
    private WxApiService wxApiService;

    @Autowired
    private IOrderService orderService;

    @Autowired
    private IPayMerchantService payMerchantService;

    @Autowired
    private IPayBoxService payBoxService;

    @Autowired
    private RedisUtil<String> redisUtil;

    private static final String WX_PAY_JSAPI = "JSAPI";

    private static final String SIGN_TYPE = "MD5";

    private static final String SUCCESS = "success";

    @Value("${payConfig.wxPay.appletNotifyUrl}")
    private String notifyUrl;

    @Override
    public Boolean payApplicationBan(String orderId, String appId) {
        PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(orderId);
        if (platformOrderDTO == null) {
            return Boolean.FALSE;
        }
        try {
            payBoxRpc.payBoxBan(appId);
        } catch (Exception e) {
            log.error("调用后台支付盒子应用禁用异常, appId : {}, e : {}", appId, e.getMessage());
        }
        return Boolean.TRUE;
    }

    @Override
    public PayApplicationDTO getPayApplicationByAppId(String appId) {
        PayApplication payApplication = super.getOne(new LambdaQueryWrapper<PayApplication>()
                .eq(PayApplication::getAppId, appId));
        if (payApplication == null) {
            log.error("参数错误, 支付商城小程序信息不存在, appId : {}", appId);
            throw new BaseException("参数错误, 支付商城小程序信息不存在");
        }
        return BeanUtil.copy(payApplication, PayApplicationDTO.class);
    }

    @Override
    public PayApplicationDTO getPayApplicationByPayBoxId(Long payBoxId) {
        PayBoxDTO payBoxDTO = payBoxService.getById(payBoxId);
        PayApplication payApplication = super.getOne(new LambdaQueryWrapper<PayApplication>()
                .eq(PayApplication::getAppId, payBoxDTO.getAppId()));
        if (payApplication == null) {
            log.error("参数错误, 支付商城小程序信息不存在, payBoxId : {}", payBoxId);
            throw new BaseException("参数错误, 支付商城小程序信息不存在");
        }
        return BeanUtil.copy(payApplication, PayApplicationDTO.class);
    }

    @Override
    public ResultVO<Map> appletStorePay(MiniAppPayParam payParam, HttpServletRequest request) {
        //查询商城小程序信息
        PayApplication payApplication = super.getOne(new LambdaQueryWrapper<PayApplication>()
                .eq(PayApplication::getAppId, payParam.getAppId()));
        if (payApplication == null) {
            throw new BaseException("参数错误, 支付商城小程序信息不存在");
        }
        //查询商户信息
        PayMerchantDTO payMerchantDTO = payMerchantService.getByMerchantNo(payApplication.getMerchantNo());
        //下单参数
        Map<String, String> payConfigMap = JsonUtil.toMap(payMerchantDTO.getPayConfig(), Map.class, String.class);
        //获取用户openId
        String openId = wxApiService.getOpenIdByType(payParam.getCode(), payApplication.getAppId(),
                payApplication.getAppSecret(), payApplication.getType()).get("openid");
        //订单id
        String orderId = this.getOrderNum(openId);
        try {
            //下单参数
            Map<String, String> paramMap = new HashMap<>(9);
            paramMap.put("appId", payApplication.getAppId());
            paramMap.put("mchId", payMerchantDTO.getMerchantNo());
            paramMap.put("productName", payParam.getProductName());
            paramMap.put("orderId", orderId);
            paramMap.put("totalFee", payParam.getTotalFee());
            paramMap.put("ip", IpUtil.getRealIp(request));
            paramMap.put("openId", openId);
            paramMap.put("apiKey", payConfigMap.get("apiKey"));
            Map<String, String> attachMap = new HashMap<>(3);
            attachMap.put("appId", payApplication.getAppId());
            attachMap.put("orderId", orderId);
            attachMap.put("merchantNo", payMerchantDTO.getMerchantNo());
            paramMap.put("attach", JsonUtil.toString(attachMap));
            log.error("下单参数, paramMap : {}", JsonUtil.toString(paramMap));
            //下单
            Map<String, String> successMap = this.unifiedOrder(paramMap);
            //构造支付参数
            Map<Object, Object> payParamMap = this.payParamMap(orderId, payApplication.getAppId(), payConfigMap.get("apiKey"), successMap);
            //添加过期缓存
            this.orderExpire(orderId, payApplication.getAppId());
            //返回
            return ResultVO.ok(payParamMap);
        } catch (Exception e) {
            log.error("微信小程序支付通信异常, 订单号：{}, e : {}", orderId, e.getMessage());
            return ResultVO.fail("微信小程序支付通信异常");
        }
    }

    private void orderExpire(String orderId, String appId) {
        //过期时间
        double expire = (double) DateUtil.localDateTimeToMilli(LocalDateTime.now().plusMinutes(2));
        //参数
        String param = orderId + "_" + appId;
        //设置缓存
        redisUtil.addZSet(RedisKeyConstant.ORDER_EXPIRE, param, expire);
        log.error("商城小程序订单添加过期缓存, orderId : {}, expire : {}", orderId, expire);
    }

    @Override
    public String appletStoreNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {

        log.error("小程序商城支付回调");

        // 读取参数
        InputStream inputStream = request.getInputStream();
        String requestStr = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
        // 解析xml成map
        Map<String, String> packageParams = XmlUtil.xmlToMap(requestStr);
        //获取订单信息
        String attachStr = packageParams.get("attach");
        Map<String, String> attachStrMap = JsonUtil.toMap(attachStr, Map.class, String.class);
        if (attachStrMap == null || attachStrMap.isEmpty()) {
            log.info("回调参数中attach值为空");
            return null;
        }
        //商户号id
        String merchantNo = attachStrMap.get("merchantNo");
        PayMerchant payMerchant = payMerchantService.getOne(new LambdaQueryWrapper<PayMerchant>()
                .eq(PayMerchant::getMerchantNo, merchantNo));
        Map<String, String> payConfigMap = JsonUtil.toMap(payMerchant.getPayConfig(), Map.class, String.class);
        // 判断签名是否正确
        String mySign = WxPayUtil.generateSignature(packageParams, payConfigMap.get("apiKey"));
        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<>();
        xmlMap.put("return_code", "SUCCESS");
        xmlMap.put("return_msg", "OK");
        // 处理业务完毕
        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<Object, Object> payParamMap(String orderId, String appId, String apiKey, Map<String, String> successMap) {
        //支付id
        String prepayId = successMap.get("prepay_id");
        // 时间戳转化成字符串，否则前端wx.requestPayment方法会报签名错误
        String createTime = String.valueOf(System.currentTimeMillis() / 1000);
        // 随机字符串
        String nonceStr = WxPayUtil.generateNonceStr();
        // 获取统一下单回调结果, 二次签名
        HashMap<String, String> map = new HashMap<>(5);
        map.put("appId", appId);
        map.put("nonceStr", nonceStr);
        map.put("timeStamp", createTime);
        map.put("package", "prepay_id=" + prepayId);
        map.put("signType", SIGN_TYPE);
        // 再次签名sign，这个签名用于小程序端调用支付方法
        String sign = WxPayUtil.generateSignature(map, apiKey);
        //组装返回前端支付参数
        Map<Object, Object> payParamMap = new HashMap<>(5);
        payParamMap.put("paySign", sign);
        payParamMap.put("signType", SIGN_TYPE);
        payParamMap.put("timeStamp", createTime);
        payParamMap.put("package", successMap);
        payParamMap.put("nonceStr", nonceStr);
        payParamMap.put("orderId", orderId);
        //返回数据
        return payParamMap;
    }

    private String getOrderNum(String openId) {
        //时间（精确到毫秒）
        DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
        String localDate = LocalDateTime.now().format(ofPattern);
        //3位随机数
        String randomNumeric = RandomStringUtils.randomNumeric(3);
        return localDate + Objects.hash(openId) + randomNumeric;
    }

    private Map<String, String> unifiedOrder(Map<String, String> paramMap) {
        try {
            Map<String, String> paramData = new HashMap<>(13);
            paramData.put("appid", paramMap.get("appId"));
            paramData.put("mch_id", paramMap.get("mchId"));
            paramData.put("nonce_str", WxPayUtil.generateNonceStr());
            paramData.put("body", paramMap.get("productName"));
            paramData.put("out_trade_no", paramMap.get("orderId"));
            paramData.put("fee_type", "CNY");
            paramData.put("total_fee", WxPayUtil.subZeroAndDot(String.valueOf(Float.parseFloat(paramMap.get("totalFee")) * 100)));
            paramData.put("spbill_create_ip", paramMap.get("ip"));
            paramData.put("notify_url", notifyUrl);
            paramData.put("trade_type", WX_PAY_JSAPI);
            paramData.put("attach", paramMap.get("attach"));
            paramData.put("sign_type", SIGN_TYPE);
            paramData.put("openid", paramMap.get("openId"));
            //接口签名
            String sign = WxPayUtil.generateSignature(paramData, paramMap.get("apiKey"));
            paramData.put("sign", sign);
            // 下单, 获取结果
            String result = HttpUtil.postData(WxPayConstants.UNIFIED_ORDER_URL, XmlUtil.mapToXml(paramData));
            Map<String, String> successMap = XmlUtil.xmlToMap(result);
            //成功, 返回结果
            if (SUCCESS.equalsIgnoreCase(successMap.get("return_code"))
                    && successMap.get("return_code").equals(successMap.get("result_code"))) {
                return successMap;
            }
            //下单失败
            log.error("微信小程序支付通信异常, paramData : {}", JsonUtil.toString(paramData));
            throw new BaseException("微信小程序支付下单失败");
        } catch (Exception e) {
            log.error("微信小程序支付通信异常, 订单号：{}, e : {}", paramMap.get("orderId"), e.getMessage());
            throw new BaseException("微信小程序支付下单异常");
        }
    }

    @Override
    public void appletStoreCancel(String param) {
        //参数切割
        String[] split = param.split("_");
        //小程序appId
        String orderId = split[0];
        String appId = split[1];
        //查询商城小程序信息
        PayApplication payApplication = super.getOne(new LambdaQueryWrapper<PayApplication>()
                .eq(PayApplication::getAppId, appId));
        if (payApplication == null) {
            return;
        }
        //查询商户信息
        PayMerchantDTO payMerchantDTO = payMerchantService.getByMerchantNo(payApplication.getMerchantNo());
        //商户支付配置
        Map<String, String> payConfigMap = JsonUtil.toMap(payMerchantDTO.getPayConfig(), Map.class, String.class);
        //关闭订单参数
        Map<String, String> payParamMap = new HashMap<>(3);
        payParamMap.put("appid", appId);
        payParamMap.put("mch_id", payMerchantDTO.getMerchantNo());
        payParamMap.put("out_trade_no", orderId);
        payParamMap.put("apiKey", payConfigMap.get("apiKey"));
        //关闭订单
        this.closeOrder(payParamMap);
    }

    private void closeOrder(Map<String, String> paramMap) {
        try {
            Map<String, String> paramData = new HashMap<>(6);
            paramData.put("appid", paramMap.get("appid"));
            paramData.put("mch_id", paramMap.get("mch_id"));
            paramData.put("out_trade_no", paramMap.get("out_trade_no"));
            paramData.put("nonce_str", WxPayUtil.generateNonceStr());
            paramData.put("sign_type", SIGN_TYPE);
            //接口签名
            String sign = WxPayUtil.generateSignature(paramData, paramMap.get("apiKey"));
            paramData.put("sign", sign);
            //取消订单, 获取结果
            String result = HttpUtil.postData(WxPayConstants.CLOSE_ORDER_URL, XmlUtil.mapToXml(paramData));
            Map<String, String> successMap = XmlUtil.xmlToMap(result);
            // 结果状态码
            String resultCode = successMap.get("result_code");
            // 返回状态码
            String returnCode = successMap.get("return_code");
            //成功, 返回结果
            if (SUCCESS.equalsIgnoreCase(returnCode) && returnCode.equals(resultCode)) {
                log.error("微信支付关闭订单成功, out_trade_no：{}", paramMap.get("out_trade_no"));
                return;
            }
            log.error("微信支付关闭订单失败, paramData：{}, successMap : {}", JsonUtil.toString(paramData), JsonUtil.toString(successMap));
        } catch (Exception e) {
            log.error("微信支付关闭订单异常, 订单号：{}, e : {}", paramMap.get("out_trade_no"), e.getMessage());
        }
    }
}
