
package com.tmerclub.cloud.payment.manager;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.github.binarywang.wxpay.service.WxPayService;
import com.tmerclub.cloud.api.payment.allinpay.member.resp.BindCard;
import com.tmerclub.cloud.api.payment.allinpay.order.resp.DepositApplyResp;
import com.tmerclub.cloud.api.payment.allinpay.order.resp.PayByBackSmsResp;
import com.tmerclub.cloud.api.payment.allinpay.order.resp.WithdrawApplyResp;
import com.tmerclub.cloud.api.payment.constant.PaySysType;
import com.tmerclub.cloud.api.platform.feign.ConfigFeignClient;
import com.tmerclub.cloud.api.user.bo.BalancePayBO;
import com.tmerclub.cloud.api.user.bo.BalanceRefundBO;
import com.tmerclub.cloud.api.user.feign.UserBalanceLogFeignClient;
import com.tmerclub.cloud.common.bean.Allinpay;
import com.tmerclub.cloud.common.constant.AllinpayConstant;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.constant.PayType;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.util.BeanUtil;
import com.tmerclub.cloud.common.util.IpHelper;
import com.tmerclub.cloud.common.util.Json;
import com.tmerclub.cloud.payment.allinpay.OpenClient;
import com.tmerclub.cloud.payment.allinpay.constant.AllinpayPlatformAccountSet;
import com.tmerclub.cloud.payment.allinpay.member.constant.VisitSourceType;
import com.tmerclub.cloud.payment.allinpay.order.req.*;
import com.tmerclub.cloud.payment.allinpay.order.resp.*;
import com.tmerclub.cloud.payment.allinpay.order.req.*;
import com.tmerclub.cloud.payment.allinpay.order.resp.*;
import com.tmerclub.cloud.payment.allinpay.service.AllinpayOrderService;
import com.tmerclub.cloud.payment.bo.PayInfoBO;
import com.tmerclub.cloud.payment.bo.RefundInfoBO;
import com.tmerclub.cloud.payment.config.AllinpayConfig;
import com.tmerclub.cloud.payment.config.WxConfig;
import org.apache.dubbo.config.annotation.DubboReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.time.LocalTime;
import java.util.*;

/**
 * 统一支付工具
 *
 * @author tmerclub
 */
@Service
public class AllinpayManager {

    @Autowired
    private AllinpayOrderService allinpayOrderService;
    @Autowired
    private WxConfig wxConfig;
    @DubboReference
    private ConfigFeignClient configFeignClient;
    @DubboReference
    private UserBalanceLogFeignClient userBalanceLogFeignClient;
    @Autowired
    private AllinpayConfig allinpayConfig;

    private static final String WX_SUCCESS_XML = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
    private static final String ALI_SUCCESS = "success";
    public static final String SUCCESS = "SUCCESS";
    private static final String TRADE_SUCCESS = "TRADE_SUCCESS";
    private static final String TRADE_STATUS_KEY = "trade_status";
    private static final String REFUND = "refund_fee";
    private static final String ALLINPAY = "allinpay";


    private static final Logger logger = LoggerFactory.getLogger(AllinpayManager.class);

    public ServerResponseEntity<?> doAllinPay(PayInfoBO payInfo) {
        if (payInfo.getPayAmount() > Constant.MAX_WECHA_TPAY_AMOUNT) {
            throw new LuckException("已超出微信支付的最大金额，请选择其他付款方式");
        }
        PayType payType = PayType.instance(payInfo.getPayType());
        if (Objects.isNull(payType)) {
            throw new LuckException("选择的支付方式不存在，请重新选择");
        }
        if (Objects.isNull(payInfo.getValidateType())) {
            payInfo.setValidateType(0L);
            if (Objects.equals(payType, PayType.BALANCE)) {
                payInfo.setValidateType(2L);
            }
        }
        // 充值申请
        if (payInfo.getIsRecharge() != null && payInfo.getIsRecharge()) {
            DepositApplyResp resp = this.depositApply(payInfo);
            return ServerResponseEntity.success(resp);
        }
        // 消费申请（平台会员购买）
        if (payInfo.getIsVip() != null && payInfo.getIsVip()) {
            ConsumeApplyResp resp = this.consumeApply(payInfo);
            return ServerResponseEntity.success(resp);
        }
        ApplicationCollection applicationCollection = new ApplicationCollection();
        applicationCollection.setBizOrderNo(payInfo.getPayId().toString());
        applicationCollection.setPayerId(payInfo.getUserId());
        if (CollectionUtil.isNotEmpty(payInfo.getReceiveList())) {
            List<ApplicationCollection.Reciever> recieverList = BeanUtil.mapAsList(payInfo.getReceiveList(), ApplicationCollection.Reciever.class);
            applicationCollection.setRecieverList(recieverList);
        }
        applicationCollection.setTradeCode("3001");
        applicationCollection.setAmount(payInfo.getPayAmount());
        applicationCollection.setFee(0L);
        applicationCollection.setBackUrl(payInfo.getApiNoticeUrl());
        applicationCollection.setFrontUrl(payInfo.getReturnUrl());
        applicationCollection.setOrderExpireDatetime(DateUtil.offsetMinute(new Date(), Constant.CANCEL_ORDER).toString());
        applicationCollection.setIndustryCode("2522");
        applicationCollection.setIndustryName("其他");
        applicationCollection.setSource(Long.parseLong(payInfo.getSystemType().toString()));
        applicationCollection.setExtendInfo(payInfo.getExtendInfo());
        applicationCollection.setValidateType(payInfo.getValidateType());
        return doPay(applicationCollection, payInfo);
    }


    private ServerResponseEntity<?> doPay(ApplicationCollection applicationCollection, PayInfoBO payInfo) {
        payInfo.setPaySysType(PaySysType.ALLINPAY.value());
        ServerResponseEntity<String> configResp = configFeignClient.getConfig(Constant.ALLINPAY_CONFIG);
        Allinpay allinpay = Json.parseObject(configResp.getData(), Allinpay.class);
        applicationCollection.setPayMethod(buildPayMethod(payInfo, allinpay));
        ApplicationCollectionResp applicationCollectionResp = allinpayOrderService.agentCollectApply(applicationCollection);
        // 防止响应数据中没有bizOrderNo，重新赋值下
        applicationCollection.setBizOrderNo(payInfo.getPayId().toString());
        // 扫码支付信息/ JS支付串信息（微信、支付宝、QQ钱包）/微信小程序/微信原生H5支付串信息/支付宝原生APP支付串信息
        // 微信小程序，公众号，扫码，h5,以及支付宝电脑,手机网站都是返回payInfo
        return ServerResponseEntity.success(applicationCollectionResp);
    }

    public boolean isWxPay(PayType payType) {
        return (Objects.equals(payType, PayType.WECHATPAY)
                || Objects.equals(payType, PayType.WECHATPAY_SWEEP_CODE)
                || Objects.equals(payType, PayType.WECHATPAY_H5)
                || Objects.equals(payType, PayType.WECHATPAY_MP)
                || Objects.equals(payType, PayType.WECHATPAY_APP));
    }

    public boolean isAliPay(PayType payType) {
        return (Objects.equals(payType, PayType.ALIPAY_H5)
                || Objects.equals(payType, PayType.ALIPAY)
                || Objects.equals(payType, PayType.ALIPAY_APP));
    }

    public void batchShareAccountToShop(BatchAgentPay batchAgentPay) {
        try {
            BatchAgentPayResp agentPayResp = allinpayOrderService.batchAgentPay(batchAgentPay);
        } catch (Exception e) {
            logger.error("异常：", e);
        }
    }


    public boolean doRefund(RefundInfoBO refundInfo) {
        if (Objects.equals(refundInfo.getRefundAmount(), 0L)) {
            return true;
        }
        Refund refund = new Refund();
        refund.setBizOrderNo(refundInfo.getRefundId() + StrUtil.UNDERLINE + refundInfo.getRefundNumber());
        refund.setOriBizOrderNo(refundInfo.getPayId().toString());
        // 退款收款人，必须是原订单中的付款方
        refund.setBizUserId(refundInfo.getUserId().toString());
        refund.setRefundType("D0");
        refund.setBackUrl(refundInfo.getNotifyUrl());
        refund.setAmount(refundInfo.getRefundAmount());
        // bizUserId不为空时，需要退款账户集编号
        if (Objects.nonNull(refundInfo.getBizUserId())) {
            // 托管代收订单中的收款人的退款金额，通常是商家
            List<Refund.RefundAccountInfo> refundList = new ArrayList<>(1);
            Refund.RefundAccountInfo refundAccountInfo = new Refund.RefundAccountInfo();
            refundAccountInfo.setBizUserId(AllinpayConstant.SHOP + refundInfo.getBizUserId());
            refundAccountInfo.setAmount(refundInfo.getRefundAmount());
            refundList.add(refundAccountInfo);
            refund.setRefundList(refundList);
        }
        try {
            RefundResp agentPayResp = allinpayOrderService.doRefund(refund);
            // 如果是余额支付回退余额
            if (Objects.equals(refundInfo.getPayType(), PayType.BALANCE)) {
                // 注意 只进行退款是不需要回调的 这个参数要传到余额支付那，不然会有问题
                // 退款金额
                BalanceRefundBO balanceRefundBO = new BalanceRefundBO();
                balanceRefundBO.setRefundId(refundInfo.getRefundId());
                balanceRefundBO.setPayId(refundInfo.getPayId());
                balanceRefundBO.setChangeBalance(refundInfo.getRefundAmount());
                balanceRefundBO.setUserId(refundInfo.getUserId());
                balanceRefundBO.setRefundNumber(refundInfo.getRefundNumber());
                balanceRefundBO.setPaySysType(PaySysType.ALLINPAY.value());
                ServerResponseEntity<Void> responseEntity = userBalanceLogFeignClient.doRefund(balanceRefundBO);
                if (!responseEntity.isSuccess()) {
                    throw new LuckException(ResponseEnum.EXCEPTION);
                }
            }
        } catch (Exception e) {
            logger.error("退款失败，Mq会多次回调：{}", refundInfo.getRefundId().toString());
            // 因为这个退款是mq调用的，所以如果一次退款失败，就调用多次，做好幂等的操作
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        return false;
    }

    public boolean execApplicationTransfer(Long payId, String bizUserId, Long payAmount) {
        if (Objects.equals(payAmount, 0L)) {
            return true;
        }
        ServerResponseEntity<String> configResp = configFeignClient.getConfig(Constant.ALLINPAY_CONFIG);
        if (!configResp.isSuccess()) {
            logger.error("平台转账失败,配置不存在：{}", configResp.getMsg());
            return false;
        }
        Allinpay allinpay = Json.parseObject(configResp.getData(), Allinpay.class);
        ApplicationTransfer applicationTransfer = new ApplicationTransfer();
        applicationTransfer.setBizTransferNo(payId.toString());
        // 标准余额账户集
        applicationTransfer.setSourceAccountSetNo(AllinpayPlatformAccountSet.STANDARD_BALANCE.value().toString());
        applicationTransfer.setTargetBizUserId(bizUserId);
        applicationTransfer.setAmount(payAmount);
        applicationTransfer.setTargetAccountSetNo(allinpay.getAccountSetNo());
        try {
            ApplicationTransferResp applicationTransferResp = allinpayOrderService.applicationTransfer(applicationTransfer);
            return true;
        } catch (Exception e) {
            logger.error("平台转账失败：{}", e.getMessage());
            logger.error("异常：", e);
        }
        return false;
    }

    /**
     * 提现申请
     * 通商云发送并验证短信验证码，有效期3分钟。
     */
    public WithdrawApplyResp withdrawApply(Long payId, String bizUserId, Long amount, String backUrl, BindCard bindCard, Integer source, Long validateType) {
        ServerResponseEntity<String> configResp = configFeignClient.getConfig(Constant.ALLINPAY_CONFIG);
        if (!configResp.isSuccess()) {
            throw new LuckException("提现失败,配置不存在：" + configResp.getMsg());
        }
        Allinpay allinpay = Json.parseObject(configResp.getData(), Allinpay.class);
        // 提现校验
        this.checkAllowWithdraw(amount);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR, 24);
        Date date = calendar.getTime();
        String orderExpireDatetime = sdf.format(date);
        OpenClient client = allinpayConfig.getClient();
        WithdrawApplyReq withdrawApplyReq = new WithdrawApplyReq();
        withdrawApplyReq.setBizOrderNo(payId.toString());
        withdrawApplyReq.setBizUserId(bizUserId);
        withdrawApplyReq.setAccountSetNo(allinpay.getAccountSetNo());
        withdrawApplyReq.setAmount(amount);
        withdrawApplyReq.setFee(0L);
        withdrawApplyReq.setValidateType(validateType);
        withdrawApplyReq.setBackUrl(backUrl);
        withdrawApplyReq.setOrderExpireDatetime(orderExpireDatetime);
        withdrawApplyReq.setBankCardNo(client.encrypt(bindCard.getBankCardNo()));
        withdrawApplyReq.setBankCardPro(bindCard.getBankCardPro());
        withdrawApplyReq.setIndustryCode(AllinpayConstant.INDUSTRY_CODE);
        withdrawApplyReq.setIndustryName(AllinpayConstant.INDUSTRY_NAME);
        withdrawApplyReq.setSource(source);
        WithdrawApplyResp withdrawApplyResp;
        try {
            withdrawApplyResp = allinpayOrderService.withdrawApply(withdrawApplyReq);
        } catch (Exception e) {
            throw new LuckException("提现失败：" + e.getMessage());
        }
        return withdrawApplyResp;
    }

    public PayByBackSmsResp payByBackSms(PayByBackSmsReq payByBackSmsReq) {
        if (Objects.isNull(payByBackSmsReq.getVerificationCode())) {
            throw new LuckException("短信验证码不能为空");
        }
        payByBackSmsReq.setConsumerIp(IpHelper.getIpAddr());
        PayByBackSmsResp payByBackSmsResp;
        try {
            payByBackSmsResp = allinpayOrderService.payByBackSms(payByBackSmsReq);
        } catch (Exception e) {
            throw new LuckException("确认支付失败，{}", e.getMessage());
        }
        return payByBackSmsResp;
    }

    /**
     * 构建支付方式
     * @param payInfo
     * @return
     */
    public Map<String, Object> buildPayMethod(PayInfoBO payInfo, Allinpay allinpay) {
        PayType payType = PayType.instance(payInfo.getPayType());
        if (Objects.isNull(payType)) {
            throw new LuckException("选择的支付方式不存在，请重新选择");
        }
        // 补充基础支付方式参数
        PayMethod payMethod = new PayMethod();
        payMethod.setLimitPay("no_credit");
        payMethod.setAmount(payInfo.getPayAmount());
        payMethod.setVspCusid(allinpay.getVspCusId());
        // 微信APP支付 or 微信小程序支付 or 微信公众号支付
        if (Objects.equals(payType, PayType.WECHATPAY_APP)
                || Objects.equals(payType, PayType.WECHATPAY)
                || Objects.equals(payType, PayType.WECHATPAY_MP)) {
            payMethod.setAcct(payInfo.getBizUserId());
            WxPayService wxPayService = wxConfig.getWxPayService(payType);
            payMethod.setSubAppid(wxPayService.getConfig().getAppId());
        }
        // 余额支付
        else if (Objects.equals(payType, PayType.BALANCE)) {
            List<PayMethod.BalancePay> balancePayList = new ArrayList<>(1);
            PayMethod.BalancePay balancePay = new PayMethod.BalancePay();
            balancePay.setAmount(payInfo.getPayAmount());
            balancePay.setAccountSetNo(allinpay.getAccountSetNo());
            balancePayList.add(balancePay);
            payMethod.setBalancePayList(balancePayList);
            // 并创建余额支付记录
            this.addBalanceLog(payInfo);
        }
        // 数据封装返回（余额支付单独处理）
        Map<String, Object> payMethodMap = new HashMap<>(1);
        if (Objects.equals(payType, PayType.BALANCE)) {
            payMethodMap.put(payType.getAllinpayCode(), payMethod.getBalancePayList());
        } else {
            payMethodMap.put(payType.getAllinpayCode(), payMethod);
        }
        return payMethodMap;
    }

    /**
     * 充值申请
     * @param payInfo
     * @return
     */
    public DepositApplyResp depositApply(PayInfoBO payInfo) {
        ServerResponseEntity<String> configResp = configFeignClient.getConfig(Constant.ALLINPAY_CONFIG);
        if (!configResp.isSuccess()) {
            throw new LuckException("充值失败，配置不存在：" + configResp.getMsg());
        }
        Allinpay allinpay = Json.parseObject(configResp.getData(), Allinpay.class);
        // 参数准备
        Map<String, Object> payMethodMap = this.buildPayMethod(payInfo, allinpay);
        // 构建参数（手续费暂定为0）
        DepositApply depositApply = new DepositApply();
        depositApply.setBizUserId(payInfo.getAllinpayUserId());
        depositApply.setBizOrderNo(payInfo.getPayId().toString());
        depositApply.setAmount(payInfo.getPayAmount());
        depositApply.setFee(0L);
        if (Objects.nonNull(payInfo.getValidateType())) {
            depositApply.setValidateType(payInfo.getValidateType());
        }
        depositApply.setSource(VisitSourceType.PC.value());
        depositApply.setPayMethod(JSON.toJSONString(payMethodMap));
        depositApply.setIndustryCode(AllinpayConstant.INDUSTRY_CODE);
        depositApply.setIndustryName(AllinpayConstant.INDUSTRY_NAME);
        depositApply.setFrontUrl(payInfo.getReturnUrl());
        depositApply.setBackUrl(payInfo.getApiNoticeUrl());
        depositApply.setAccountSetNo(allinpay.getAccountSetNo());
        // 发送请求，并返回结果
        return allinpayOrderService.depositApply(depositApply);
    }

    /**
     * 消费申请
     * @param payInfoBO
     * @return
     */
    public ConsumeApplyResp consumeApply(PayInfoBO payInfoBO) {
        ServerResponseEntity<String> configResp = configFeignClient.getConfig(Constant.ALLINPAY_CONFIG);
        if (!configResp.isSuccess()) {
            throw new LuckException("消费申请失败，配置不存在：" + configResp.getMsg());
        }
        Allinpay allinpay = Json.parseObject(configResp.getData(), Allinpay.class);
        ConsumeApply consumeApply = new ConsumeApply();
        Map<String, Object> payMethod = this.buildPayMethod(payInfoBO, allinpay);
        consumeApply.setPayMethod(JSON.toJSONString(payMethod));
        consumeApply.setValidateType(payInfoBO.getValidateType());
        consumeApply.setAmount(payInfoBO.getPayAmount());
        consumeApply.setBizOrderNo(payInfoBO.getPayId().toString());
        consumeApply.setFee(0L);
        consumeApply.setFrontUrl(payInfoBO.getReturnUrl());
        consumeApply.setBackUrl(payInfoBO.getApiNoticeUrl());
        consumeApply.setSource(Long.parseLong(payInfoBO.getSystemType().toString()));
        consumeApply.setIndustryCode(AllinpayConstant.INDUSTRY_CODE);
        consumeApply.setIndustryName(AllinpayConstant.INDUSTRY_NAME);
        consumeApply.setPayerId(payInfoBO.getAllinpayUserId());
        // 若是购买会员，收款方为平台
        if (payInfoBO.getIsVip() != null && payInfoBO.getIsVip()) {
            consumeApply.setRecieverId("#yunBizUserId_B2C#");
        }
        return allinpayOrderService.consumeApply(consumeApply);
    }

    /**
     * 创建余额支付记录
     * @param payInfoBO
     */
    private void addBalanceLog(PayInfoBO payInfoBO) {
        BalancePayBO balancePayBO = new BalancePayBO();
        balancePayBO.setOrderIds(payInfoBO.getOrderIds());
        balancePayBO.setChangeBalance(payInfoBO.getPayAmount());
        balancePayBO.setPayId(payInfoBO.getPayId());
        balancePayBO.setPaySysType(payInfoBO.getPaySysType());
        if (Objects.nonNull(payInfoBO.getIsVip())) {
            balancePayBO.setIsVip(payInfoBO.getIsVip());
        } else {
            balancePayBO.setIsVip(false);
        }
        ServerResponseEntity<Void> resp = userBalanceLogFeignClient.insertBalancePayLog(balancePayBO);
        if (!resp.isSuccess()) {
            throw new LuckException("创建余额支付记录失败：" + resp.getMsg());
        }
    }

    /**
     * 检查是否允许提现
     * @param amount
     */
    private void checkAllowWithdraw(Long amount) {
        // 提现时间检查
        LocalTime now = LocalTime.now();
        boolean isOutOfTime = AllinpayConstant.ALLOW_WITHDRAW_START_TIME.isAfter(now)
                || AllinpayConstant.ALLOW_WITHDRAW_END_TIME.isBefore(now);
        if (isOutOfTime) {
            throw new LuckException(String.format("允许提现的时间为：%s - %s", AllinpayConstant.ALLOW_WITHDRAW_START_TIME, AllinpayConstant.ALLOW_WITHDRAW_END_TIME));
        }
    }

    /**
     * 获取订单支付状态
     * @param payInfoBO
     * @return
     */
    public GetOrderStatusResp getOrderStatus(String bizOrderNo) {
        GetOrderStatusReq getOrderStatus = new GetOrderStatusReq();
        getOrderStatus.setBizOrderNo(bizOrderNo);
        return allinpayOrderService.getOrderStatus(getOrderStatus);
    }

}
