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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zanxiang.game.module.base.pojo.enums.GameCategoryEnum;
import com.zanxiang.game.module.base.pojo.enums.OsEnum;
import com.zanxiang.game.module.base.pojo.enums.PayDeviceEnum;
import com.zanxiang.game.module.mybatis.entity.Game;
import com.zanxiang.game.module.mybatis.entity.Order;
import com.zanxiang.game.module.mybatis.entity.OrderPayParam;
import com.zanxiang.game.module.mybatis.entity.User;
import com.zanxiang.game.module.sdk.constant.RedisKeyConstant;
import com.zanxiang.game.module.sdk.enums.ExpireTimeEnum;
import com.zanxiang.game.module.sdk.enums.OrderStateEnum;
import com.zanxiang.game.module.sdk.enums.PayTypeEnum;
import com.zanxiang.game.module.sdk.pojo.dto.GamePayWayDTO;
import com.zanxiang.game.module.sdk.pojo.dto.PayApplicationDTO;
import com.zanxiang.game.module.sdk.pojo.dto.ProductPayParamDTO;
import com.zanxiang.game.module.sdk.pojo.param.ProductPayParam;
import com.zanxiang.game.module.sdk.pojo.param.UserData;
import com.zanxiang.game.module.sdk.pojo.vo.PayParamVO;
import com.zanxiang.game.module.sdk.pojo.vo.SessionCheckVO;
import com.zanxiang.game.module.sdk.service.*;
import com.zanxiang.game.module.sdk.service.api.WxApiService;
import com.zanxiang.game.module.sdk.service.pay.PayBaseService;
import com.zanxiang.game.module.sdk.util.RedisUtil;
import com.zanxiang.game.module.sdk.util.SpringUtils;
import com.zanxiang.module.redis.service.IDistributedLockComponent;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.bean.BeanUtil;
import com.zanxiang.module.util.exception.BaseException;
import com.zanxiang.module.web.util.IpUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author : lingfeng
 * @time : 2022-09-28
 * @description : 订单支付
 */
@Slf4j
@Service
public class OrderPayServiceImpl implements IOrderPayService {

    @Autowired
    public IOrderService orderService;

    @Autowired
    public IGamePayWayService gamePayWayService;

    @Autowired
    private IOrderPayParamService orderPayParamService;

    @Autowired
    private IPayApplicationService payApplicationService;

    @Autowired
    private IUserService userService;

    @Autowired
    private RedisUtil<String> redisUtil;

    @Autowired
    private IGameService gameService;

    @Autowired
    private WxApiService wxApiService;

    @Autowired
    private IDistributedLockComponent distributedLockComponent;

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

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

    @Override
    public PayParamVO getPayParam(String code, String openId, String orderId, HttpServletRequest request) {
        log.error("获取订单支付参数, code : {}, openId : {}, orderId : {}", code, openId, orderId);
        Order order = this.orderFindAndCheck(orderId);
        OrderPayParam orderPayParam = orderPayParamService.getOne(new LambdaQueryWrapper<OrderPayParam>()
                .eq(OrderPayParam::getOrderId, order.getOrderId()));
        if (orderPayParam != null) {
            return BeanUtil.copy(orderPayParam, PayParamVO.class);
        }
        //创建支付参数
        this.payParamCreate(code, openId, order, request);
        //重新查询
        orderPayParam = orderPayParamService.getOne(new LambdaQueryWrapper<OrderPayParam>()
                .eq(OrderPayParam::getOrderId, order.getOrderId()));
        if (orderPayParam == null) {
            log.error("支付参数获取失败, orderId : {}", orderId);
            throw new BaseException("支付参数正在创建中, 请勿重复请求!");
        }
        //返回支付参数
        return BeanUtil.copy(orderPayParam, PayParamVO.class);
    }

    private void payParamCreate(String code, String openId, Order order, HttpServletRequest request) {
        //上锁
        if (!distributedLockComponent.doLock(RedisKeyConstant.GET_PAY_PARAM_LOCK + order.getOrderId(), 0L, 3L, TimeUnit.MINUTES)) {
            return;
        }
        //创建支付参数
        ProductPayParamDTO productPayParamDTO = this.transform(order, code, openId, IpUtil.getRealIp(request));
        //调起支付
        PayTypeEnum payTypeEnum = PayTypeEnum.getByPayType(productPayParamDTO.getPayWay().intValue());
        PayBaseService service = SpringUtils.getBean(payTypeEnum.getClazz());
        service.payCreate(productPayParamDTO);
    }

    private ProductPayParamDTO transform(Order order, String code, String openId, String ip) {
        return ProductPayParamDTO.builder()
                .gameId(order.getGameId())
                .userId(order.getUserId())
                .payDevice(order.getPayDeviceId())
                .deviceSystem(order.getDeviceSystem())
                .spbillCreateIp(ip)
                .outTradeNo(order.getOrderId())
                .payWay(order.getPayWayId())
                .code(code)
                .openId(openId)
                .build();
    }

    private Order orderFindAndCheck(String orderId) {
        Order order = orderService.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderId, orderId));
        if (order == null) {
            throw new BaseException("参数错误, 订单信息不存在");
        }
        if (Objects.equals(order.getStatus(), OrderStateEnum.CANCEL_PAY.getCode())) {
            throw new BaseException("订单已关闭, 请回到游戏重新下单");
        }
        if (Objects.equals(order.getStatus(), OrderStateEnum.SUCCESS_PAY.getCode())) {
            throw new BaseException("订单已支付, 请勿重复支付");
        }
        return order;
    }

    @Override
    public Long getAppletPayDevice(UserData userData) {
        log.error("获取支付类型参数, 请求信息 userData : {}", JsonUtil.toString(userData));
        //查询游戏配置的支付方式
        List<GamePayWayDTO> gamePayWayList = gamePayWayService.listOfGameId(userData.getGameId());
        //支付方式
        GamePayWayDTO gamePayWayDTO = null;
        //android用户, 游戏适配米大师支付
        if (Objects.equals(userData.getDeviceSystem(), OsEnum.SYSTEM_ANDROID.getOs())
                || Objects.equals(userData.getDeviceSystem(), OsEnum.SYSTEM_WINDOW.getOs())
                || Objects.equals(userData.getDeviceSystem(), OsEnum.SYSTEM_OHOS.getOs())
        ) {
            gamePayWayDTO = gamePayWayList.stream().filter(gamePayWay ->
                    Objects.equals(gamePayWay.getPayDeviceId(), PayDeviceEnum.MI_PAY.getPayDeviceId()))
                    .findFirst().orElse(null);
        }
        //非android用户, 或者未适配到米大师支付, 适配小程序商城支付
        if (gamePayWayDTO == null) {
            gamePayWayDTO = gamePayWayList.stream().filter(gamePayWay ->
                    Objects.equals(gamePayWay.getPayDeviceId(), PayDeviceEnum.APPLET_PAY.getPayDeviceId()))
                    .findFirst().orElse(null);
        }
        //兜底适配, 适配客服支付
        if (gamePayWayDTO == null) {
            gamePayWayDTO = gamePayWayList.stream().filter(gamePayWay ->
                    Objects.equals(gamePayWay.getPayDeviceId(), PayDeviceEnum.CUSTOM_PAY.getPayDeviceId()))
                    .findFirst().orElse(null);
        }
        //返回适配的支付方式
        if (gamePayWayDTO != null) {
            log.error("返回适配的支付方式, deviceSystem : {}, payDeviceId : {}", userData.getDeviceSystem(), gamePayWayDTO.getPayDeviceId());
            return gamePayWayDTO.getPayDeviceId();
        }
        //未匹配到任何合适的支付方式
        log.error("数据错误, 未匹配到合适的支付方式, userData : {}", JsonUtil.toString(userData));
        throw new BaseException("数据错误, 未匹配到合适的支付方式");
    }

    @Override
    public Map<String, Object> payCreate(ProductPayParam product, UserData userData) {
        //用户信息
        User user = userService.getById(userData.getUserId());
        if (user == null) {
            throw new BaseException("参数错误, 用户信息不存在");
        }
        //创建订单
        orderService.createOrder(product, user, userData);
        //判断游戏类型
        if (PayDeviceEnum.getByPayWayIdList().contains(product.getPayDevice())) {
            //查询支付配置
            GamePayWayDTO gamePayWayDTO = gamePayWayService.getGamePayWay(userData.getGameId(), product.getPayWay(), product.getPayDevice());
            //查询支付应用信息
            PayApplicationDTO payApplicationDTO = payApplicationService.getPayApplicationByPayBoxId(gamePayWayDTO.getPayBoxId());
            //构造参数信息
            Map<String, Object> paramMap = new HashMap<>(8);
            paramMap.put("appId", payApplicationDTO.getAppId());
            paramMap.put("path", payApplicationDTO.getPath());
            paramMap.put("orderId", product.getOrderId());
            paramMap.put("amount", product.getAmount());
            paramMap.put("payH5Url", this.customH5Url);
            paramMap.put("openId", user.getOpenId());
            paramMap.put("description", "购买" + product.getAmount() + "元档充值");
            paramMap.put("serverUrl", this.serverUrl.contains("test") ? this.serverUrl + "/api/sdk" : this.serverUrl + "/sdk");
            log.error("下单参数返回, paramMap : {}", JsonUtil.toString(paramMap));
            //客服支付添加redis缓存
            if (Objects.equals(product.getPayDevice(), PayDeviceEnum.CUSTOM_PAY.getPayDeviceId())) {
                //以玩家id为key, orderId为value, 设置5分钟缓存
                redisUtil.setCache(RedisKeyConstant.GAME_CUSTOM_PAY_SIGN + user.getOpenId(),
                        product.getOrderId(), ExpireTimeEnum.FIVE_MIN.getTime());
            }
            return paramMap;
        }
        //创建支付参数
        ProductPayParamDTO bo = new ProductPayParamDTO();
        bo.setGameId(userData.getGameId());
        bo.setUserId(userData.getUserId());
        bo.setPayDevice(product.getPayDevice());
        bo.setDeviceSystem(userData.getDeviceSystem());
        bo.setSpbillCreateIp(userData.getIp());
        bo.setOutTradeNo(product.getOrderId());
        bo.setPayWay(product.getPayWay());
        bo.setCode(product.getCode());
        //调起支付
        PayTypeEnum payTypeEnum = PayTypeEnum.getByPayType(bo.getPayWay().intValue());
        PayBaseService service = SpringUtils.getBean(payTypeEnum.getClazz());
        return service.payCreate(bo);
    }

    @Override
    public String notify(HttpServletRequest request, HttpServletResponse response, Integer payType) throws IOException {
        PayTypeEnum payTypeEnum = PayTypeEnum.getByPayType(payType);
        PayBaseService service = SpringUtils.getBean(payTypeEnum.getClazz());
        return service.notify(request, response);
    }

    @Override
    public Boolean payResult(String orderId) {
        Order order = orderService.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderId, orderId));
        if (order == null) {
            throw new BaseException("参数错误, 订单信息不存在");
        }
        if (Objects.equals(order.getStatus(), OrderStateEnum.SUCCESS_PAY.getCode())) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    @Override
    public SessionCheckVO sessionCheck(UserData userData) {
        User user = userService.getById(userData.getUserId());
        Game game = gameService.getById(userData.getGameId());
        //默认结果
        boolean result = Boolean.FALSE;
        //限制微信小游戏
        if (Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_WX_APPLET.getId())) {
            //限制安卓端或者PC端
            if (Objects.equals(userData.getDeviceSystem(), OsEnum.SYSTEM_ANDROID.getOs())
                    || Objects.equals(userData.getDeviceSystem(), OsEnum.SYSTEM_WINDOW.getOs())
                    || Objects.equals(userData.getDeviceSystem(), OsEnum.SYSTEM_DEVTOOLS.getOs())
                    || Objects.equals(userData.getDeviceSystem(), OsEnum.SYSTEM_OHOS.getOs())
            ) {
                result = wxApiService.checkSessionKey(userData.getGameId(), user.getOpenId(), user.getSessionKey());
            }
        }
        return SessionCheckVO.builder()
                .reLogin(result)
                .msg(result ? "点击右上角\"·●·\"形状的三个点，\n在弹出的页面下拉找到“重新进入小程序”" : null)
                .build();
    }
}
