package com.ruoyi.bst.pay.service.impl;

import com.github.pagehelper.PageHelper;
import com.ruoyi.bst.app.domain.AppVO;
import com.ruoyi.bst.app.service.AppService;
import com.ruoyi.bst.channel.domain.ChannelVO;
import com.ruoyi.bst.channel.domain.enums.ChannelApiType;
import com.ruoyi.bst.channel.service.ChannelConverter;
import com.ruoyi.bst.channel.service.ChannelService;
import com.ruoyi.bst.order.service.OrderService;
import com.ruoyi.bst.pay.domain.Pay;
import com.ruoyi.bst.pay.domain.PayQuery;
import com.ruoyi.bst.pay.domain.PayVO;
import com.ruoyi.bst.pay.domain.dto.PayRefundDTO;
import com.ruoyi.bst.pay.domain.enums.PayBstType;
import com.ruoyi.bst.pay.domain.enums.PayStatus;
import com.ruoyi.bst.pay.domain.vo.DoPayVO;
import com.ruoyi.bst.pay.interfaces.PayHandler;
import com.ruoyi.bst.pay.mapper.PayMapper;
import com.ruoyi.bst.pay.service.PayService;
import com.ruoyi.bst.refund.domain.Refund;
import com.ruoyi.bst.refund.domain.RefundVO;
import com.ruoyi.bst.refund.service.RefundConverter;
import com.ruoyi.bst.refund.service.RefundService;
import com.ruoyi.common.core.redis.RedisLock;
import com.ruoyi.common.core.redis.enums.RedisLockKey;
import com.ruoyi.common.domain.vo.LocalDateDecimalVO;
import com.ruoyi.common.domain.vo.LongDecimalVO;
import com.ruoyi.common.domain.vo.LongIntegerVO;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.pay.PayApi;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.collection.CollectionUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 支付订单Service业务层处理
 *
 * @author ruoyi
 * @date 2025-03-26
 */
@Service
@Slf4j
public class PayServiceImpl implements PayService {
    @Autowired
    private PayMapper payMapper;

    @Autowired
    private RedisLock redisLock;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private ChannelService channelService;

    @Autowired
    private AppService appService;

    @Autowired
    private ChannelConverter channelConverter;

    @Autowired
    private ScheduledExecutorService scheduledExecutorService;

    @Autowired
    private RefundService refundService;

    @Autowired
    private RefundConverter refundConverter;
    @Autowired
    private OrderService orderService;

    /**
     * 查询支付订单
     *
     * @param id 支付订单主键
     * @return 支付订单
     */
    @Override
    public PayVO selectPayById(Long id) {
        return payMapper.selectPayById(id);
    }

    /**
     * 查询支付订单列表
     *
     * @param pay 支付订单
     * @return 支付订单
     */
    @Override
    public List<PayVO> selectPayList(PayQuery pay) {
        return payMapper.selectPayList(pay);
    }

    /**
     * 新增支付订单
     *
     * @param pay 支付订单
     * @return 结果
     */
    @Override
    public int insertPay(Pay pay) {
        pay.setCreateTime(DateUtils.getNowDate());
        pay.setNo(String.valueOf(SnowFlakeUtil.newId()));
        return payMapper.insertPay(pay);
    }

    /**
     * 修改支付订单
     *
     * @param pay 支付订单
     * @return 结果
     */
    @Override
    public int updatePay(Pay pay) {
        return payMapper.updatePay(pay);
    }

    /**
     * 批量删除支付订单
     *
     * @param ids 需要删除的支付订单主键
     * @return 结果
     */
    @Override
    public int deletePayByIds(Long[] ids) {
        return payMapper.deletePayByIds(ids);
    }

    /**
     * 删除支付订单信息
     *
     * @param id 支付订单主键
     * @return 结果
     */
    @Override
    public int deletePayById(Long id) {
        return payMapper.deletePayById(id);
    }


    @Override
    public DoPayVO createPayBill(Pay pay) {
        ServiceUtil.assertion(pay == null, "待新增的支付订单不存在");
        ServiceUtil.assertion(StringUtils.isBlank(pay.getAccount()), "用户支付账号不存在");
        ServiceUtil.assertion(StringUtils.isBlank(pay.getBstType()) || pay.getBstId() == null, "待支付的业务信息不允许为空");

        // 锁
        String lockKey = pay.getBstType() + pay.getBstId();
        if (!redisLock.lock(RedisLockKey.PAY_CREATE, lockKey)) {
            return null;
        }

        try {
            // 尝试关闭旧的支付订单
            boolean close = this.closeByBstId(pay.getBstType(), pay.getBstId());
            ServiceUtil.assertion(!close, "关闭旧支付订单失败");

            return transactionTemplate.execute(status -> {
                // 插入订单
                pay.setStatus(PayStatus.PAYING.getStatus());
                int insert = this.insertPay(pay);
                ServiceUtil.assertion(insert != 1, "插入支付订单失败");

                // 调起支付
                DoPayVO vo = this.doPay(this.selectByNo(pay.getNo()));

                // 更新渠道订单号
                int update = this.updateChannelNo(pay.getNo(), vo.getPayParams());
                ServiceUtil.assertion(update != 1, "更新渠道订单号失败");

                // 3秒后。支付成功
                // 开发测试用，正式环境需要删除
//                scheduledExecutorService.schedule(() -> {
//                    this.handleSuccess(pay.getNo(), LocalDateTime.now());
//                }, 3, TimeUnit.SECONDS);

                return vo;
            });
        } finally {
            redisLock.unlock(RedisLockKey.PAY_CREATE, lockKey);
        }
    }


    private int updateChannelNo(String no, Object payParams) {
        if (StringUtils.isBlank(no) || payParams == null) {
            return 0;
        }
        // TODO 获取渠道订单号

        return 1;
    }

    @Override
    public PayVO selectByNo(String no, boolean scope) {
        if (StringUtils.isBlank(no)) {
            return null;
        }
        PayQuery query = new PayQuery();
        query.setEqNo(no);
        query.setScope(scope);
        return this.selectOne(query);
    }

    private PayVO selectOne(PayQuery query) {
        PageHelper.startPage(1, 1);
        List<PayVO> payList = this.selectPayList(query);
        return CollectionUtils.firstElement(payList);
    }


    @Override
    public boolean closeByBstId(String bstType, Long bstId) {
        if (bstId == null || bstType == null) {
            return false;
        }
        // 查询未支付、支付中的订单
        PayQuery query = new PayQuery();
        query.setBstId(bstId);
        query.setBstType(bstType);
        query.setStatusList(PayStatus.canClose());
        List<PayVO> payList = this.selectPayList(query);

        int close = this.closeAll(payList);

        return close == payList.size();
    }

    private int closeAll(List<PayVO> payList) {
        int result = 0;

        // 关闭订单
        for (PayVO pay : payList) {
            if (pay == null) {
                log.error("关闭支付订单出错，数据为空");
                continue;
            }
            try {
                int rows = this.close(pay);
                result += rows;
            } catch (Exception e) {
                log.error("关闭支付订单{}时出错:{}", pay.getId(), e.getMessage());
            }
        }
        return result;
    }

    // 关闭支付单
    private int close(PayVO pay) {
        if (pay == null) {
            return 0;
        }
        // 校验
        ServiceUtil.assertion(pay.getId() == null || StringUtils.isBlank(pay.getNo()), "支付单参数为空");
        ServiceUtil.assertion(!PayStatus.canClose().contains(pay.getStatus()), "支付单%s当前状态不允许关闭", pay.getId());

        Integer rows = transactionTemplate.execute(status -> {
            // 修改状态
            Pay closeData = new Pay();
            closeData.setStatus(PayStatus.CANCELED.getStatus());
            PayQuery updateQuery = new PayQuery();
            updateQuery.setStatus(pay.getStatus());
            updateQuery.setId(pay.getId());
            int update = payMapper.updateByQuery(closeData, updateQuery);

            if (update == 1) {
                // 关闭支付中的订单
                if (PayStatus.PAYING.getStatus().equals(pay.getStatus())) {
                    // 查询渠道信息
                    ChannelVO channel = channelService.selectChannelByChannelId(pay.getChannelId());
                    ServiceUtil.assertion(channel == null, "ID为%s的支付渠道不存在", pay.getChannelId());
                    // 获取支付API
                    PayApi payApi = ChannelApiType.getPayApi(channel.getApiType());
                    ServiceUtil.assertion(payApi == null, "支付渠道ID为%s的API获取失败", pay.getChannelId());
                    // 查询应用信息
                    AppVO app = appService.selectAppById(pay.getAppId());
                    ServiceUtil.assertion(app == null, "ID为%s的APP信息不存在", pay.getAppId());
                    // 调用关闭API
                    try {
                        payApi.closeByOutTradeNo(pay.getNo(), channelConverter.toConfig(channel, app));
                    } catch (Exception e) {
                        // 关闭失败，尝试查询订单信息，判断是否已经支付成功
                        if (payApi.isPaySuccessByOutTradeNo(pay.getNo(),
                                DateUtils.toLocalDate(pay.getCreateTime()), channelConverter.toConfig(channel, app))) {
                            throw new ServiceException("当前交易已成功，无法关闭");
                        } else {
                            throw e;
                        }
                    }
                }
            }

            return update;
        });

        // 若关闭订单成功，则处理业务
        if (rows != null && rows == 1) {
            PayVO newPay = this.selectPayById(pay.getId());
            PayBstType bstType = PayBstType.parse(newPay.getBstType());
            if (bstType != null && bstType.getPayHandler() != null) {
                PayHandler payHandler = SpringUtils.getBean(bstType.getPayHandler());
                boolean bstResult = payHandler.onClose(newPay);
                ServiceUtil.assertion(!bstResult, "业务处理失败");
            }
        }

        return rows == null ? 0 : rows;
    }

    // 执行支付
    private DoPayVO doPay(PayVO pay) {
        DoPayVO vo = new DoPayVO();
        vo.setPay(pay);

        // 若支付金额为0，则不调起支付
        if (pay.getAmount().compareTo(BigDecimal.ZERO) == 0) {
            vo.setPayParams("支付金额为0，无需调起支付");
            // 异步处理支付成功
            scheduledExecutorService.schedule(() -> {
                this.handleSuccess(pay.getNo(), LocalDateTime.now());
            }, 1L, TimeUnit.SECONDS);
        } else {
            // 获取渠道信息
            ChannelVO channel = channelService.selectChannelByChannelId(pay.getChannelId());
            ServiceUtil.assertion(channel == null, "ID为%s的支付渠道不存在", pay.getChannelId());
            ServiceUtil.assertion(channel.getEnabled() == null || !channel.getEnabled(), "支付渠道【%s】不可用",
                    channel.getName());

            // 获取API
            PayApi payApi = ChannelApiType.getPayApi(channel.getApiType());
            ServiceUtil.assertion(payApi == null, "当前支付渠道开发中，请尝试其他渠道");

            // 获取应用信息
            AppVO app = appService.selectAppById(pay.getAppId());
            ServiceUtil.assertion(app == null, "ID为%s的应用不存在", pay.getAppId());

            // 调用API发起支付
            vo.setPayParams(payApi.pay(pay, channelConverter.toConfig(channel, app)));

            // 异步刷新支付结果
            scheduledExecutorService.schedule(() -> {
                this.refreshPayResultBeforeExpire(pay.getNo());
            }, 10, TimeUnit.SECONDS);
        }

        return vo;
    }

    @Override
    public void refreshPayResultBeforeExpire(String no) {
        if (StringUtils.isBlank(no)) {
            return;
        }
        // 刷新支付结果
        this.refreshPayResult(no);
        // 获取刷新后的订单
        PayVO pay = this.selectByNo(no);
        if (pay == null) {
            return;
        }

        // 若支付单已过期且未支付成功，则关闭订单
        Duration duration = Duration.between(LocalDateTime.now(), pay.getExpireTime());
        long seconds = duration.getSeconds();
        if (seconds <= 0 && !PayStatus.payedList().contains(pay.getStatus())) {
            this.close(pay);
            return;
        }

        // 未支付成功，并且还未过期，则继续延迟刷新
        if (PayStatus.PAYING.getStatus().equals(pay.getStatus()) && seconds > 0) {
            log.info("{}未支付成功，继续延迟刷新", pay.getId());
            scheduledExecutorService.schedule(() -> {
                this.refreshPayResultBeforeExpire(no);
            }, 20, TimeUnit.SECONDS);
        }
    }

    @Override
    public List<LocalDateDecimalVO> selectPayAmount(PayQuery payQuery) {
        return payMapper.selectPayAmount(payQuery);
    }

    @Override
    public List<LongDecimalVO> selectSumOfAmountGroupByStoreId(PayQuery query) {
        return payMapper.selectSumOfAmountGroupByStoreId(query);
    }


    /**
     * 刷新支付结果
     *
     * @param no 支付单号
     */
    @Override
    public void refreshPayResult(String no) {
        if (StringUtils.isBlank(no)) {
            return;
        }

        PayVO pay = this.selectByNo(no);
        if (pay == null) {
            return;
        }

        // 若支付金额为0，则直接操作支付成功
        if (pay.getAmount().compareTo(BigDecimal.ZERO) == 0) {
            this.handleSuccess(pay, LocalDateTime.now());
            return;
        }
        // 获取渠道
        ChannelVO channel = channelService.selectChannelByChannelId(pay.getChannelId());
        ServiceUtil.assertion(channel == null, "ID为%s的支付渠道不存在", pay.getChannelId());
        // 获取支付API
        PayApi payApi = ChannelApiType.getPayApi(channel.getApiType());
        ServiceUtil.assertion(payApi == null, "支付渠道ID为%s的API获取失败", pay.getChannelId());
        // 获取应用信息
        AppVO app = appService.selectAppById(pay.getAppId());
        ServiceUtil.assertion(app == null, "ID为%s的APP信息不存在", pay.getAppId());
        // 获取支付结果，若成功则处理支付成功
        Object result = payApi.queryByOutTradeNo(pay.getNo(), DateUtils.toLocalDate(pay.getCreateTime()),
                channelConverter.toConfig(channel, app));
        if (payApi.isPaySuccess(result)) {
            this.handleSuccess(pay, payApi.getPayTime(result));
        }
    }


    @Override
    public void handleSuccess(String no, LocalDateTime payTime) {
        if (no == null) {
            return;
        }
        PayVO pay = this.selectByNo(no);
        this.handleSuccess(pay, payTime);
    }

    private void handleSuccess(PayVO pay, LocalDateTime payTime) {
        ServiceUtil.assertion(pay == null || pay.getId() == null, "支付订单不允许为空");

        // 若不是支付成功的状态，则修改为支付成功
        if (!PayStatus.payedList().contains(pay.getStatus())) {
            ServiceUtil.assertion(!PayStatus.PAYING.getStatus().equals(pay.getStatus()),
                    "ID为%s的支付订单不是正在支付的支付订单", pay.getId());

            // 修改支付订单状态
            Pay data = new Pay();
            data.setStatus(PayStatus.PAYED.getStatus());
            data.setPayTime(payTime);
            PayQuery query = new PayQuery();
            query.setStatus(PayStatus.PAYING.getStatus());
            query.setId(pay.getId());
            int update = payMapper.updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "支付订单状态已改变，请稍后再试");
        }

        // 获取最新支付单数据
        PayVO bill = this.selectPayById(pay.getId());
        // 若为成功，则处理业务
        if (PayStatus.PAYED.getStatus().equals(bill.getStatus())) {
            PayBstType bstType = PayBstType.parse(pay.getBstType());
            if (bstType != null && bstType.getPayHandler() != null) {
                PayHandler payHandler = SpringUtils.getBean(bstType.getPayHandler());
                boolean bstResult = payHandler.onPaySuccess(bill);
                ServiceUtil.assertion(!bstResult, "业务处理失败");
            }
        }
    }


    @Override
    public int refund(PayRefundDTO dto) {
        // 校验订单
        PayVO pay = this.selectPayById(dto.getId());
        if (pay == null) {
            return 0;
        }

        BigDecimal totalRefundAmount = MathUtils.addDecimal(dto.getRefundAmount(), pay.getRefunded(), pay.getRefunding());
        ServiceUtil.assertion(totalRefundAmount.compareTo(pay.getAmount()) > 0, "ID为%s的支付单累计退款金额不允许大于支付金额", pay.getId());
        ServiceUtil.assertion(!PayStatus.canRefund().contains(pay.getStatus()), "ID为%s的支付单当前状态不允许退款", pay.getId());

        Integer result = transactionTemplate.execute(status -> {
            // 修改订单状态
            if (!PayStatus.REFUNDED.getStatus().equals( pay.getStatus())) {
                Pay data = new Pay();
                data.setStatus(PayStatus.REFUNDED.getStatus());
                PayQuery query = new PayQuery();
                query.setId(pay.getId());
                query.setStatusList(PayStatus.canRefund());
                int rows = payMapper.updateByQuery(data, query);
                ServiceUtil.assertion(rows != 1, "修改ID为%s的支付单状态失败", pay.getId());
            }

            // 增加退款中金额
            int addRefunding = payMapper.addRefundingAmount(dto.getId(), dto.getRefundAmount());
            ServiceUtil.assertion(addRefunding != 1, "记录退款中的金额失败");

            // 增加退款的次数
            int addRefundNum = orderService.addRefundNum(dto.getId(),dto.getNumber());
            ServiceUtil.assertion(addRefundNum != 1, "记录退款中的次数失败");

            // 创建退款订单
            Refund refundPo = refundConverter.toPo(dto, pay);
            int refund = refundService.createRefund(refundPo);
            ServiceUtil.assertion(refund != 1, "申请退款失败");

            return refund;
        });

        return result == null ? 0 : result;
    }

    @Override
    public int handleRefundSuccess(RefundVO refund) {
        if (refund == null) {
            return 0;
        }

        Integer result = transactionTemplate.execute(status -> {
            // 记录已退款金额，并修改状态
            int recordAmount = payMapper.recordRefundAmount(refund.getPayId(), refund.getAmount());
            ServiceUtil.assertion(recordAmount != 1, "修改ID为%s的支付单退款金额失败", refund.getPayId());

            // 修改业务
            PayVO pay = this.selectPayById(refund.getPayId());
            PayBstType bstType = PayBstType.parse(pay.getBstType());
            if (bstType != null && bstType.getPayHandler() != null) {
                PayHandler payHandler = SpringUtils.getBean(bstType.getPayHandler());
                boolean bstResult = payHandler.onRefundSuccess(refund);
                ServiceUtil.assertion(!bstResult, "退款业务修改失败");
            }

            return recordAmount;
        });

        return result == null ? 0 : result;
    }
}
