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.pojo.dto.H5GameConfigDTO;
import com.zanxiang.game.module.base.pojo.enums.HttpStatusEnum;
import com.zanxiang.game.module.base.pojo.enums.PayDeviceEnum;
import com.zanxiang.game.module.base.pojo.enums.PayWayEnum;
import com.zanxiang.game.module.mybatis.entity.Game;
import com.zanxiang.game.module.mybatis.entity.GameApplet;
import com.zanxiang.game.module.mybatis.entity.GameExt;
import com.zanxiang.game.module.mybatis.entity.Order;
import com.zanxiang.game.module.mybatis.mapper.GameAppletMapper;
import com.zanxiang.game.module.sdk.enums.OrderStateEnum;
import com.zanxiang.game.module.sdk.pojo.dto.*;
import com.zanxiang.game.module.sdk.pojo.param.UserData;
import com.zanxiang.game.module.sdk.pojo.vo.GameInitVO;
import com.zanxiang.game.module.sdk.service.*;
import com.zanxiang.game.module.sdk.service.api.WxApiService;
import com.zanxiang.game.module.sdk.service.pay.MiPayService;
import com.zanxiang.game.module.sdk.util.SignUtil;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.bean.BeanUtil;
import com.zanxiang.module.util.exception.BaseException;
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.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.util.function.Tuples;

import java.math.BigDecimal;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author : lingfeng
 * @time : 2022-07-08
 * @description : 微信小游戏
 */
@Slf4j
@Service
public class GameAppletServiceImpl extends ServiceImpl<GameAppletMapper, GameApplet> implements IGameAppletService {

    @Autowired
    private IUserService userService;

    @Autowired
    private IOrderService orderService;

    @Autowired
    private WxApiService wxApiService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private IGamePayWayService gamePayWayService;

    @Autowired
    private IPayApplicationService payApplicationService;

    @Autowired
    private IGameService gameService;

    @Autowired
    private IGameExtService gameExtService;

    @Autowired
    private MiPayService miPayService;

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

    @Override
    public String appletMsgCheck(String appId, String signature, String timestamp, String nonce, String echoStr) throws Exception {
        log.error("验证参数, appId : {}, signature : {}, timestamp : {}, nonce : {}, echoStr : {}", appId, signature, timestamp, nonce, echoStr);
        GameAppletDTO gameAppletDTO = this.getByAppId(appId);
        GameAppletDTO.MsgConfigBean msgConfigBean = gameAppletDTO.getMsgConfigBean();
        if (msgConfigBean == null) {
            throw new BaseException("参数错误, 小游戏应用缺少消息推送配置");
        }
        String mySignature = SignUtil.SHA1(msgConfigBean.getMsgPushToken(), timestamp, nonce);
        log.error("计算出来的签名, signature : {},  mySignature : {}", signature, mySignature);
        if (!Objects.equals(mySignature, signature)) {
            throw new BaseException("验证签名不匹配");
        }
        return echoStr;
    }

    @Override
    public String appletMsg(String appId, String signature, String timestamp, String nonce, String postData) throws Exception {
        log.error("接收到事件消息, appId : {}, signature : {}, timestamp : {}, nonce : {}, postData : {}",
                appId, signature, timestamp, nonce, postData);
        String result = HttpStatusEnum.SUCCESS.getMsg();
        if (Strings.isBlank(postData)) {
            return result;
        }
        GameAppletDTO gameAppletDTO = this.getByAppId(appId);
        GameAppletDTO.MsgConfigBean msgConfigBean = gameAppletDTO.getMsgConfigBean();
        if (msgConfigBean == null) {
            return result;
        }
        String mySignature = SignUtil.SHA1(msgConfigBean.getMsgPushToken(), timestamp, nonce);
        if (!Objects.equals(mySignature, signature)) {
            log.error("计算出来的签名不匹配, msgConfigBean : {}, signature : {},  mySignature : {}", JsonUtil.toString(msgConfigBean),
                    signature, mySignature);
            return result;
        }
        //消息内容
        AppletMsgDTO appletMsgDTO = JsonUtil.toObj(postData, AppletMsgDTO.class);
        //用户客服支付会话
        if (Objects.equals(appletMsgDTO.getMsgType(), AppletMsgDTO.MSG_TYPE_TEXT)
                && Objects.equals(appletMsgDTO.getContent(), AppletMsgDTO.MSG_CONTENT_PAY)) {
            return this.customPayMessage(gameAppletDTO, appletMsgDTO);
        }
        //米大师支付回调事件
        if (Objects.equals(appletMsgDTO.getMsgType(), AppletMsgDTO.MSG_TYPE_EVENT)
                && Objects.equals(appletMsgDTO.getEvent(), AppletMsgDTO.EVENT_MI_PAY_CALL_BACK)) {
            return this.miPayMessage(appletMsgDTO, gameAppletDTO);
        }
        //其他消息不处理, 直接返回成功
        return result;
    }

    private String miPayMessage(AppletMsgDTO appletMsgDTO, GameAppletDTO gameAppletDTO) {
        log.error("米大师支付回调 appletMsgDTO : {}", JsonUtil.toString(appletMsgDTO));
        Map<String, Object> failResultMap = new HashMap<>(2);
        failResultMap.put("ErrCode", 99999L);
        failResultMap.put("ErrMsg", "internal error");
        String failResult = JsonUtil.toString(failResultMap);
        Map<String, Object> successResultMap = new HashMap<>(2);
        successResultMap.put("ErrCode", 0L);
        successResultMap.put("ErrMsg", "Success");
        String successResult = JsonUtil.toString(successResultMap);
        //通知内容
        AppletMsgDTO.MiniGameBean miniGame = appletMsgDTO.getMiniGame();
        //判断是否为模拟数据, 模拟数据直接返回, 不进行业务处理
        if (Objects.equals(miniGame.getIsMock(), Boolean.TRUE)) {
            log.error("模拟调试返回返回成功, 返回结果 successResult : {}", successResult);
            return successResult;
        }
        //支付内容
        String payload = miniGame.getPayload();
        log.error("-----------> 支付信息 payload : {}", payload);
        AppletMsgDTO.PayloadBean payloadBean = JsonUtil.toObj(payload, AppletMsgDTO.PayloadBean.class);
        log.error("-----------> payload 对象转换成功 payloadBean : {}", JsonUtil.toString(payloadBean));
        if (payloadBean == null) {
            log.error("米大师支付回调参数错误, 没有支付信息 appletMsgDTO : {}", JsonUtil.toString(appletMsgDTO));
            return failResult;
        }
        //商户订单号和交易订单号判断
        AppletMsgDTO.WeChatPayInfoBean weChatPayInfo = payloadBean.getWeChatPayInfo();
        if (weChatPayInfo == null || Strings.isBlank(weChatPayInfo.getMchOrderNo())
                || Strings.isBlank(weChatPayInfo.getTransactionId())) {
            log.error("米大师支付回调参数错误, 没有支付信息 appletMsgDTO : {}", JsonUtil.toString(appletMsgDTO));
            return failResult;
        }
        //查询游戏小程序信息
        String appKey = gameAppletDTO.getMiPayConfigBean().getAppKey();
        //计算签名
        String mySign = miPayService.calcSignature(appletMsgDTO.getEvent() + "&" + miniGame.getPayload(), appKey);
        //签名不匹配
        if (!Objects.equals(mySign, miniGame.getPayEventSig())) {
            log.error("米大师支付回调签名不匹配, mySign : {}, PayEventSig : {}, appKey : {}",
                    mySign, miniGame.getPayEventSig(), appKey);
            return failResult;
        }
        log.error("米大师支付回调签名匹配成功, 开始计算业务数据--------------->");
        //更新订单信息
        Boolean miPayNotifyResult = miPayService.miPayNotify(payloadBean.getOutTradeNo(),
                Tuples.of(weChatPayInfo.getMchOrderNo(), weChatPayInfo.getTransactionId()));
        //放业务更新结果
        return Objects.equals(miPayNotifyResult, Boolean.TRUE) ? successResult : failResult;
    }

    private String customPayMessage(GameAppletDTO gameAppletDTO, AppletMsgDTO appletMsgDTO) {
        //用户信息
        UserDTO userDTO = userService.getUserByOpenId(gameAppletDTO.getGameId(), appletMsgDTO.getFromUserName());
        //查询用户最新客服支付订单
        Order order = orderService.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getUserId, userDTO.getId())
                .eq(Order::getStatus, OrderStateEnum.READY_PAY.getCode())
                .eq(Order::getPayWayId, PayWayEnum.WX_PAY.getPayWayId())
                .eq(Order::getPayDeviceId, PayDeviceEnum.CUSTOM_PAY.getPayDeviceId())
                .orderByDesc(Order::getCreateTime)
                .last("limit 1"));
        if (order != null) {
            //发送客服消息
            return this.sendCustomMessage(gameAppletDTO, userDTO.getOpenId(), order);
        }
        return HttpStatusEnum.SUCCESS.getMsg();
    }

    private String sendCustomMessage(GameAppletDTO gameAppletDTO, String openId, Order order) {
        //查询订单支付方式
        GamePayWayDTO gamePayWayDTO = gamePayWayService.getById(order.getGamePayWayId());
        //查询支付应用信息
        PayApplicationDTO payApplicationDTO = payApplicationService.getPayApplicationByPayBoxId(gamePayWayDTO.getPayBoxId());
        //客服支付链接显示图片地址
        String thumbUrl = gamePayWayDTO.getThumbUrl();
        //支付配置参数判断
        if (Strings.isBlank(thumbUrl)) {
            log.error("客服消息卡片图片地址不存在, gamePayWayDTO : {}", JsonUtil.toString(gamePayWayDTO));
            return HttpStatusEnum.SUCCESS.getMsg();
        }
        //订单金额
        BigDecimal amount = order.getAmount();
        //构造跳转链接url
        URI url = UriComponentsBuilder.fromHttpUrl(this.customH5Url)
                .queryParam("appId", payApplicationDTO.getAppId())
                .queryParam("orderId", order.getOrderId())
                .queryParam("amount", amount)
                .queryParam("description", "购买" + amount + "元档充值")
                .build().toUri();
        //link参数构造
        Map<String, Object> linkMap = new HashMap<>(4);
        linkMap.put("title", "点我充值");
        linkMap.put("description", "点我充值" + amount + "元,用于购买" + amount + "元档充值");
        linkMap.put("url", url);
        linkMap.put("thumb_url", thumbUrl);
        //发送客服消息
        return this.sendCustomMessageApi(gameAppletDTO, openId, "link", linkMap);
    }

    private String sendCustomMessageApi(GameAppletDTO gameAppletDTO, String openId, String msgType, Map<String, Object> msgMap) {
        //客服消息参数构造
        Map<String, Object> paramMap = new HashMap<>(3);
        paramMap.put("touser", openId);
        paramMap.put("msgtype", msgType);
        paramMap.put(msgType, msgMap);
        log.error("客服消息发送参数, paramMap : {}", JsonUtil.toString(paramMap));
        //获取接口token
        String accessToken = wxApiService.getAccessToken(gameAppletDTO.getAppId(), gameAppletDTO.getAppSecret());
        URI uri = UriComponentsBuilder.fromHttpUrl("https://api.weixin.qq.com/cgi-bin/message/custom/send")
                .queryParam("access_token", accessToken)
                .build().toUri();
        // 发送请求
        String result = restTemplate.postForObject(uri, paramMap, String.class);
        log.error("客服消息发送结果, result : {}", result);
        return HttpStatusEnum.SUCCESS.getMsg();
    }

    @Override
    public GameAppletDTO getByGameId(Long gameId) {
        GameApplet gameApplet = super.getOne(new LambdaQueryWrapper<GameApplet>()
                .eq(GameApplet::getGameId, gameId));
        if (gameApplet == null) {
            throw new BaseException("参数错误, 游戏小程序信息不存在");
        }
        return BeanUtil.copy(gameApplet, GameAppletDTO.class);
    }

    @Override
    public GameAppletDTO getByAppId(String appId) {
        GameApplet gameApplet = super.getOne(new LambdaQueryWrapper<GameApplet>()
                .eq(GameApplet::getAppId, appId));
        if (gameApplet == null) {
            throw new BaseException("参数错误, 游戏小程序信息不存在");
        }
        return BeanUtil.copy(gameApplet, GameAppletDTO.class);
    }

    @Override
    public GameInitVO h5AndAppletInit(UserData userData) {
        //游戏
        Game game = gameService.getById(userData.getGameId());
        if (game == null) {
            throw new BaseException("参数错误, 游戏信息不存在");
        }
        //H5游戏配置
        String h5GameConfig = game.getH5GameConfig();
        //游戏应用信息
        GameApplet gameApplet = super.getOne(new LambdaQueryWrapper<GameApplet>()
                .eq(GameApplet::getGameId, userData.getGameId()));
        GameExt gameExt = gameExtService.getByGameId(userData.getGameId());
        //构造初始化信息
        return GameInitVO.builder()
                .appId(gameApplet == null ? null : gameApplet.getAppId())
                .appKey(gameExt == null ? null : gameExt.getAppKey())
                .gameName(game.getName())
                .isPut(game.getIsPut())
                .h5GameConfigDTO(Strings.isBlank(h5GameConfig) ? null : JsonUtil.toObj(h5GameConfig, H5GameConfigDTO.class))
                .build();
    }
}
