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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.zanxiang.game.back.base.pojo.enums.OrderStatusEnum;
import com.zanxiang.game.module.base.pojo.enums.HttpStatusEnum;
import com.zanxiang.game.module.mybatis.entity.Order;
import com.zanxiang.game.module.sdk.enums.OrderStateEnum;
import com.zanxiang.game.module.sdk.listener.OrderPaySuccessEvent;
import com.zanxiang.game.module.sdk.pojo.dto.*;
import com.zanxiang.game.module.sdk.service.IGameAppletService;
import com.zanxiang.game.module.sdk.service.IOrderService;
import com.zanxiang.game.module.sdk.service.IUserService;
import com.zanxiang.game.module.sdk.service.api.WxApiService;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.URIUtil;
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.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
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 : 2023-05-26
 * @description : 米大师支付2.0
 */
@Slf4j
@Service
public class MiPayService extends PayBaseService {

    private static final String SIGN_HMACSHA256 = "HMACSHA256";

    private static final String MI_PAY_HOST = "https://api.weixin.qq.com";

    private static final String BALANCE_URL = "/wxa/game/getbalance";

    private static final String PAY_URL = "/wxa/game/pay";

    @Value("${payConfig.miPay.isSand}")
    private Integer isSand;

    @Autowired
    private IOrderService orderService;

    @Autowired
    private IGameAppletService gameAppletService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private WxApiService wxApiService;

    @Autowired
    private IUserService userService;

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 支付配置
     */
    private MiPayConfigDTO config;

    @Override
    public Map<String, Object> create(ProductPayParamDTO product, GamePayWayDTO gamePayWayDTO) {
        //查询订单
        PlatformOrderDTO orderInfo = orderService.getByOrderId(product.getOutTradeNo());
        if (Objects.isNull(orderInfo)) {
            throw new BaseException(HttpStatusEnum.ORDER_NO_FIND.getMsg());
        }
        if (orderInfo.getGameId() == null || orderInfo.getGameId() == 0) {
            throw new BaseException(HttpStatusEnum.ORDER_GAME_ID_IS_NULL.getMsg());
        }
        this.configInit(product.getGameId());
        //获取用户openId
        UserDTO userDTO = userService.getUserByUserId(product.getUserId());
        String openId = userDTO.getOpenId();
        String sessionKey = userDTO.getSessionKey();
        //获取米大师钱包余额
        Long balance = this.midasGetBalance(openId, sessionKey);
        //返回充值参数
        Map<String, Object> payParamMap = new HashMap<>(9);
        payParamMap.put("currencyType", "CNY");
        payParamMap.put("mode", "game");
        payParamMap.put("zoneId", "1");
        payParamMap.put("offerId", config.getOfferId());
        payParamMap.put("env", String.valueOf(this.isSand));
        payParamMap.put("buyQuantity", this.getOrderAmount(orderInfo.getAmount()));
        payParamMap.put("status", "1");
        payParamMap.put("balance", String.valueOf(balance));
        payParamMap.put("orderId", product.getOutTradeNo());
        payParamMap.put("platform", "android");
        //更新订单商户信息
        orderService.update(new LambdaUpdateWrapper<Order>()
                .set(Order::getMerchantNo, config.getOfferId())
                .set(Order::getStatus, OrderStateEnum.WAIT_PAY.getCode())
                .eq(Order::getOrderId, product.getOutTradeNo()));
        log.error("米大师支付返回参数 payParamMap : {}", JsonUtil.toString(payParamMap));
        return payParamMap;
    }

    private Long midasGetBalance(String openId, String sessionKey) {
        //接口参数
        Map<String, Object> postBodyMap = new HashMap<>(5);
        postBodyMap.put("offer_id", config.getOfferId());
        postBodyMap.put("openid", openId);
        postBodyMap.put("ts", System.currentTimeMillis() / 1000);
        postBodyMap.put("zone_id", "1");
        postBodyMap.put("env", this.isSand);
        String postBody = JsonUtil.toString(postBodyMap);
        //接口token
        String accessToken = wxApiService.getAccessToken(config.getAppId(), config.getSecret());
        //登录签名
        String signature = calcSignature(postBody, sessionKey);
        //支付签名
        String paySig = calcPaySig(MiPayService.BALANCE_URL, postBody, config.getAppKeyByIsSand(this.isSand));
        //请求头参数
        Map<String, String> headParamMap = new HashMap<>(3);
        headParamMap.put("access_token", accessToken);
        headParamMap.put("signature", signature);
        headParamMap.put("pay_sig", paySig);
        //调接口返回
        String url = URIUtil.fillUrlParams(MiPayService.MI_PAY_HOST + MiPayService.BALANCE_URL, headParamMap, Boolean.FALSE);
        Map<String, String> resultMap = this.miPayApi(url, postBody);
        return Long.valueOf(resultMap.get("balance"));
    }

    private Tuple2<Long, String> midasPay(String openId, String sessionKey, long amount, String orderId) {
        //接口参数
        Map<String, Object> postBodyMap = new HashMap<>(7);
        postBodyMap.put("openid", openId);
        postBodyMap.put("offer_id", config.getOfferId());
        postBodyMap.put("ts", System.currentTimeMillis() / 1000);
        postBodyMap.put("zone_id", "1");
        postBodyMap.put("env", this.isSand);
        postBodyMap.put("amount", amount);
        postBodyMap.put("bill_no", orderId);
        String postBody = JsonUtil.toString(postBodyMap);
        //接口token
        String accessToken = wxApiService.getAccessToken(config.getAppId(), config.getSecret());
        //登录签名
        String signature = this.calcSignature(postBody, sessionKey);
        //支付签名
        String paySig = this.calcPaySig(MiPayService.PAY_URL, postBody, config.getAppKeyByIsSand(this.isSand));
        //请求头参数
        Map<String, String> headParamMap = new HashMap<>(3);
        headParamMap.put("access_token", accessToken);
        headParamMap.put("signature", signature);
        headParamMap.put("pay_sig", paySig);
        //调接口返回
        String url = URIUtil.fillUrlParams(MiPayService.MI_PAY_HOST + MiPayService.PAY_URL, headParamMap, Boolean.FALSE);
        Map<String, String> resultMap = this.miPayApi(url, postBody);
        //返回扣款参数
        return Tuples.of(Long.valueOf(resultMap.get("balance")), resultMap.get("bill_no"));
    }

    private Map<String, String> miPayApi(String url, String postBody) {
        String result;
        try {
            result = restTemplate.postForObject(url, postBody, String.class);
        } catch (Exception e) {
            log.error("米大师接口调用异常, url : {}, postBody : {}, e : {}", url, postBody, e.getMessage());
            throw new BaseException("米大师接口调用异常");
        }
        if (Strings.isBlank(result)) {
            log.error("米大师接口调用失败, 返回结果为空, url : {}, postBody : {}", url, postBody);
            throw new BaseException("米大师接口调用失败, 返回结果为空");
        }
        Map<String, String> resultMap = JsonUtil.toMap(result, Map.class, String.class, String.class);
        //返回结果成功
        if (Objects.equals("0", resultMap.get("errcode"))) {
            return resultMap;
        }
        //返回结果失败
        log.error("米大师接口调用返回结果失败, url : {}, postBody : {}, errcode : {}, errmsg : {}", url, postBody,
                resultMap.get("errcode"), resultMap.get("errmsg"));
        throw new BaseException("米大师接口调用返回结果失败");
    }

    private void configInit(Long gameId) {
        //查询游戏小程序信息
        GameAppletDTO gameAppletDTO = gameAppletService.getByGameId(gameId);
        GameAppletDTO.MiPayConfigBean miPayConfigBean = gameAppletDTO.getMiPayConfigBean();
        if (miPayConfigBean == null) {
            log.error("参数错误, 米大师支付参数不存在, gameId : {}", gameId);
            throw new BaseException("参数错误, 米大师支付参数不存在");
        }
        //支付配置赋值
        this.config = MiPayConfigDTO.builder()
                .appId(gameAppletDTO.getAppId())
                .secret(gameAppletDTO.getAppSecret())
                .offerId(miPayConfigBean.getAppId())
                .appKey(miPayConfigBean.getAppKey())
                .appKeyDev(miPayConfigBean.getAppKeyDev())
                .scale(miPayConfigBean.getScale())
                .build();
    }

    private String calcPaySig(String uri, String postBody, String appKey) {
        String needSignMsg = uri + "&" + postBody;
        return this.calcSignature(needSignMsg, appKey);
    }

    public String calcSignature(String postBody, String sessionKey) {
        try {
            Mac hmacSha256 = Mac.getInstance(MiPayService.SIGN_HMACSHA256);
            SecretKeySpec secretKey = new SecretKeySpec(sessionKey.getBytes(StandardCharsets.UTF_8), MiPayService.SIGN_HMACSHA256);
            hmacSha256.init(secretKey);
            byte[] signatureBytes = hmacSha256.doFinal(postBody.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(signatureBytes);
        } catch (Exception e) {
            log.error("米大师签名计算异常, postBody : {}, sessionKey : {}", postBody, sessionKey);
            throw new BaseException("米大师签名计算异常");
        }
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }

    public Map<String, Object> notify(String orderId) {
        log.error("米大师支付回调 orderId : {}", orderId);
        //查询订单
        PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(orderId);
        if (platformOrderDTO == null) {
            log.error("参数错误, 订单信息不存在! orderId : {}", orderId);
            throw new BaseException("参数错误, 订单信息不存在");
        }
        this.configInit(platformOrderDTO.getGameId());
        //获取用户openId
        UserDTO userDTO = userService.getUserByUserId(platformOrderDTO.getUserId());
        String sessionKey = userDTO.getSessionKey();
        String openId = userDTO.getOpenId();
        //获取米大师钱包余额
        Long balance = this.midasGetBalance(openId, sessionKey);
        //订单代币金额
        long orderAmount = this.getOrderAmount(platformOrderDTO.getAmount());
        //余额不足, 无法扣除
        if (balance < orderAmount) {
            log.error("米大师游戏币不足, 无法扣除! balance : {}, orderId : {}", balance, orderId);
            throw new BaseException("米大师游戏币不足, 无法扣除!");
        }
        //余额充足, 直接扣除
        Tuple2<Long, String> tuple2 = this.midasPay(openId, sessionKey, orderAmount, platformOrderDTO.getOrderId());
        //支付成功
        this.paySuccess(platformOrderDTO.getOrderId(), platformOrderDTO.getAmount().toString(), tuple2.getT2());
        //返回参数
        Map<String, Object> payParamMap = new HashMap<>(2);
        payParamMap.put("status", "0");
        payParamMap.put("billNo", tuple2.getT2());
        return payParamMap;
    }

    public boolean miPayNotify(String orderId, Tuple2<String, String> weChatPayInfoTuple2) {
        //查询订单
        PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(orderId);
        if (platformOrderDTO == null) {
            log.error("参数错误, 订单信息不存在! orderId : {}", orderId);
            return Boolean.FALSE;
        }
        //订单已支付成功, 重复消息
        if (Objects.equals(platformOrderDTO.getStatus(), OrderStatusEnum.SUCCESS_PAY.getValue())) {
            return Boolean.TRUE;
        }
        this.configInit(platformOrderDTO.getGameId());
        //获取用户openId
        UserDTO userDTO = userService.getUserByUserId(platformOrderDTO.getUserId());
        String openId = userDTO.getOpenId();
        String sessionKey = userDTO.getSessionKey();
        //获取米大师钱包余额
        Long balance = this.midasGetBalance(openId, sessionKey);
        //订单代币金额
        long orderAmount = this.getOrderAmount(platformOrderDTO.getAmount());
        //余额不足, 无法扣除
        if (balance < orderAmount) {
            log.error("米大师游戏币不足, 无法扣除! balance : {}, orderId : {}", balance, orderId);
            return Boolean.FALSE;
        }
        //余额充足, 直接扣除
        Tuple2<Long, String> tuple2 = this.midasPay(openId, sessionKey, orderAmount, platformOrderDTO.getOrderId());
        log.error("米大师余额扣除返回, orderId : {}, tuple2 : {}", orderId, JsonUtil.toString(tuple2));
        //支付成功逻辑
        this.miPaySuccess(platformOrderDTO.getOrderId(), platformOrderDTO.getAmount().toString(), weChatPayInfoTuple2);
        //返回成功
        return Boolean.TRUE;
    }

    private void miPaySuccess(String orderNo, String realAmount, Tuple2<String, String> weChatPayInfoTuple2) {
        Order order = orderService.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderId, orderNo));
        //订单不存在或者是已经支付成功的状态，直接返回成功
        if (order == null || order.getStatus().equals(OrderStateEnum.SUCCESS_PAY.getCode())) {
            return;
        }
        //订单更新
        orderService.update(new LambdaUpdateWrapper<Order>()
                .set(Order::getRealAmount, new BigDecimal(realAmount))
                .set(Order::getMiPayMerchantNo, weChatPayInfoTuple2.getT1())
                .set(Order::getMerchantOrderNo, weChatPayInfoTuple2.getT2())
                .set(Order::getStatus, OrderStateEnum.SUCCESS_PAY.getCode())
                .set(Order::getPayTime, LocalDateTime.now())
                .eq(Order::getOrderId, orderNo));
        //发送订单履约消息
        OrderPaySuccessEvent orderPaySuccessEvent = new OrderPaySuccessEvent(this, order.getOrderId());
        applicationContext.publishEvent(orderPaySuccessEvent);
    }

    @Override
    public String notify(HttpServletRequest request, HttpServletResponse response) {
        throw new BaseException("米大师支付不存在通用异步回调");
    }

    @Override
    public void closeOrder(PlatformOrderDTO platformOrderDTO) {
        String orderId = platformOrderDTO.getOrderId();
        log.error("米大师超时未支付, 订单取消, orderId : {}", orderId);
        orderService.update(new LambdaUpdateWrapper<Order>()
                .set(Order::getStatus, OrderStateEnum.CANCEL_PAY.getCode())
                .set(Order::getUpdateTime, LocalDateTime.now())
                .eq(Order::getOrderId, orderId));
    }

    private long getOrderAmount(BigDecimal amount) {
        int scale = this.config.getScale() == null ? 1 : this.config.getScale();
        return amount.longValue() * scale;
    }
}
