package edu.whut.infrastructure.adapter.repository;

import com.alibaba.fastjson2.JSON;
import edu.whut.domain.activity.model.entity.UserGroupBuyOrderDetailEntity;
import edu.whut.domain.trade.adapter.repository.ITradeRepository;
import edu.whut.domain.trade.model.aggregate.GroupBuyOrderAggregate;
import edu.whut.domain.trade.model.aggregate.GroupBuyRefundAggregate;
import edu.whut.domain.trade.model.aggregate.GroupBuyTeamSettlementAggregate;
import edu.whut.domain.trade.model.entity.*;
import edu.whut.domain.trade.model.valobj.*;
import edu.whut.infrastructure.dao.IGroupBuyActivityDao;
import edu.whut.infrastructure.dao.IGroupBuyOrderDao;
import edu.whut.infrastructure.dao.IGroupBuyOrderListDao;
import edu.whut.infrastructure.dao.INotifyTaskDao;
import edu.whut.infrastructure.dao.po.GroupBuyActivity;
import edu.whut.infrastructure.dao.po.GroupBuyOrder;
import edu.whut.infrastructure.dao.po.GroupBuyOrderList;
import edu.whut.infrastructure.dao.po.NotifyTask;
import edu.whut.infrastructure.dcc.DCCService;
import edu.whut.infrastructure.redis.IRedisService;
import edu.whut.types.common.Constants;
import edu.whut.types.enums.ActivityStatusEnumVO;
import edu.whut.types.enums.GroupBuyOrderStatusEnumVO;
import edu.whut.types.enums.ResponseCode;
import edu.whut.types.exception.AppException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * 仓储实现：负责把领域对象 <-> 数据库 PO 的转换与持久化；
 * 不做任何业务规则判断，只保证数据一致性。
 */
@Slf4j
@Repository
@RequiredArgsConstructor
// todo:用户退单后，他的参与活动次数应该释放，应该还能再次参加拼团
public class TradeRepository implements ITradeRepository {

    private final IGroupBuyOrderDao groupBuyOrderDao;

    private final IGroupBuyOrderListDao groupBuyOrderListDao;

    private final IGroupBuyActivityDao groupBuyActivityDao;

    private final INotifyTaskDao notifyTaskDao;

    private final DCCService dccService;

    // 拼团成功消息通知的routing_key
    @Value("${spring.rabbitmq.config.producer.topic_team_success.routing_key}")
    private String topic_team_success;

    // 用户退单消息通知的routing_key
    @Value("${spring.rabbitmq.config.producer.topic_team_refund.routing_key}")
    private String topic_team_refund;

    private final IRedisService redisService;

    /**
     * 根据外部交易号 & 用户id 查询未支付的锁单记录（用于幂等）
     */
    @Override
    public MarketPayOrderEntity queryMarketPayOrderEntityByOutTradeNo(String userId, String outTradeNo) {
        // 只拼装查询条件需要的字段
        GroupBuyOrderList query = new GroupBuyOrderList();
        query.setUserId(userId);
        query.setOutTradeNo(outTradeNo);

        GroupBuyOrderList res = groupBuyOrderListDao.queryGroupBuyOrderRecordByOutTradeNo(query);
        if (res == null) {
            return null;
        }

        // 组装领域对象返回，只返回上层真正关心的属性
        return MarketPayOrderEntity.builder()
                .teamId(res.getTeamId())
                .orderId(res.getOrderId())
                .originalPrice(res.getOriginalPrice())
                .deductionPrice(res.getDeductionPrice())
                .payPrice(res.getPayPrice())
                .tradeOrderStatusEnumVO(TradeOrderStatusEnumVO.valueOf(res.getStatus()))
                .build();
    }

    /**
     * 锁定营销预支付订单
     * <p>
     * - 新团：自己创建团，写入 group_buy_order，再写入 group_buy_order_list<br>
     * - 老团：更新 lock_count，满员时抛异常<br>
     * 事务超时 500 ms，成功后返回锁定好的订单信息
     */
    @Transactional(timeout = 500)
    @Override
    public MarketPayOrderEntity lockMarketPayOrder(GroupBuyOrderAggregate agg) {

        // ------------- 聚合拆分（已通过领域校验）-------------
        UserEntity        user           = agg.getUserEntity();
        PayActivityEntity activity       = agg.getPayActivityEntity();
        PayDiscountEntity discount       = agg.getPayDiscountEntity();
        NotifyConfigVO    notifyConfigVO = discount.getNotifyConfigVO();
        Integer           userTakeTimes  = agg.getUserTakeOrderCount();

        /* ---------- 1. 处理 group_buy_order（团单主表） ---------- */
        String teamId = activity.getTeamId();
        Date   groupValidEndTime;   // ← 统一定义，后面给明细单使用

        // 1.1 自己发起拼团
        if (StringUtils.isBlank(teamId)) {
            teamId = RandomStringUtils.randomNumeric(8);

            Date currentDate = new Date();
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(currentDate);
            calendar.add(Calendar.MINUTE, activity.getValidTime());

            GroupBuyOrder orderPo = GroupBuyOrder.builder()
                    .teamId(teamId)
                    .activityId(activity.getActivityId())
                    .source(discount.getSource())
                    .channel(discount.getChannel())
                    .originalPrice(discount.getOriginalPrice())
                    .deductionPrice(discount.getDeductionPrice())
                    .payPrice(discount.getOriginalPrice().subtract(discount.getDeductionPrice()))
                    .targetCount(activity.getTargetCount())
                    .completeCount(0)
                    .lockCount(1)                       // 首单已锁定
                    .validStartTime(currentDate)
                    .validEndTime(calendar.getTime())
                    .notifyType(notifyConfigVO.getNotifyType().getCode())
                    .notifyUrl(notifyConfigVO.getNotifyUrl())
                    .build();

            groupBuyOrderDao.insert(orderPo);
            groupValidEndTime = orderPo.getValidEndTime(); // ← 直接取
        }
        // 1.2 参加已有团队
        else {
            int rows = groupBuyOrderDao.updateAddLockCount(teamId);
            if (rows != 1) {
                throw new AppException(ResponseCode.E0005); // 拼团已满员
            }
            // 更新成功后再查主单获取 validEndTime
            GroupBuyOrder groupOrder = groupBuyOrderDao.queryGroupBuyTeamByTeamId(teamId);
            groupValidEndTime = groupOrder.getValidEndTime();
        }

        /* ---------- 2. 写入 group_buy_order_list（团单明细表） ---------- */
        String orderId = RandomStringUtils.randomNumeric(12); // 订单号

        Date orderStartTime = new Date();                     // 开始时间
        Calendar endTimeCal = Calendar.getInstance();
        endTimeCal.setTime(orderStartTime);
        endTimeCal.add(Calendar.MINUTE, 15);
        Date orderEndTime = endTimeCal.getTime();

        GroupBuyOrderList orderListPo = GroupBuyOrderList.builder()
                .userId(user.getUserId())
                .teamId(teamId)
                .orderId(orderId)
                .activityId(activity.getActivityId())
                .startTime(orderStartTime)
                .endTime(orderEndTime)
                .validEndTime(groupValidEndTime)              // ← 新增字段
                .goodsId(discount.getGoodsId())
                .source(discount.getSource())
                .channel(discount.getChannel())
                .originalPrice(discount.getOriginalPrice())
                .deductionPrice(discount.getDeductionPrice())
                .payPrice(discount.getPayPrice())
                .status(TradeOrderStatusEnumVO.CREATE.getCode()) // 0 = 初始锁定
                .outTradeNo(discount.getOutTradeNo())
                .bizId(activity.getActivityId()               // 活动id_用户id_参与次数
                        + Constants.UNDERLINE
                        + user.getUserId()
                        + Constants.UNDERLINE
                        + (userTakeTimes + 1))
                .build();

        try {
            groupBuyOrderListDao.insert(orderListPo);
        } catch (DuplicateKeyException e) {
            throw new AppException(ResponseCode.INDEX_EXCEPTION); // 幂等冲突
        }

        /* ---------- 3. 返回领域对象 ---------- */
        return MarketPayOrderEntity.builder()
                .orderId(orderId)
                .originalPrice(discount.getOriginalPrice())
                .deductionPrice(discount.getDeductionPrice())
                .payPrice(discount.getPayPrice())
                .tradeOrderStatusEnumVO(TradeOrderStatusEnumVO.CREATE)
                .teamId(teamId)
                .build();
    }


    /**
     * 查询拼团进度（当前已完成、目标、已锁定人数）
     */
    @Override
    public GroupBuyProgressVO queryGroupBuyProgress(String teamId) {
        GroupBuyOrder po = groupBuyOrderDao.queryGroupBuyProgress(teamId);
        if (po == null) {
            return null;
        }
        return GroupBuyProgressVO.builder()
                .completeCount(po.getCompleteCount())
                .targetCount(po.getTargetCount())
                .lockCount(po.getLockCount())
                .build();
    }

    /**
     * 根据 activityId 查询拼团活动实体。
     */
    @Override
    public GroupBuyActivityEntity queryGroupBuyActivityEntityByActivityId(Long activityId) {
        GroupBuyActivity groupBuyActivity = groupBuyActivityDao.queryGroupBuyActivityByActivityId(activityId);
        GroupBuyActivityEntity groupBuyActivityEntity = new GroupBuyActivityEntity();
        BeanUtils.copyProperties(groupBuyActivity, groupBuyActivityEntity);
        groupBuyActivityEntity.setStatus(
                ActivityStatusEnumVO.valueOf(groupBuyActivity.getStatus())
        );
        return groupBuyActivityEntity;
    }

    /**
     * 查询用户在某活动中已下单次数（用于限购校验）。
     */
    @Override
    public Integer queryOrderCountByActivityId(Long activityId, String userId) {
        GroupBuyOrderList groupBuyOrderListReq = new GroupBuyOrderList();
        groupBuyOrderListReq.setActivityId(activityId);
        groupBuyOrderListReq.setUserId(userId);
        return groupBuyOrderListDao.queryOrderCountByActivityId(groupBuyOrderListReq);
    }

    /**
     * 查询团队订单（主表）信息。
     */
    @Override
    public GroupBuyTeamEntity queryGroupBuyTeamByTeamId(String teamId) {
        GroupBuyOrder groupBuyOrder = groupBuyOrderDao.queryGroupBuyTeamByTeamId(teamId);
        return GroupBuyTeamEntity.builder()
                .teamId(groupBuyOrder.getTeamId())
                .activityId(groupBuyOrder.getActivityId())
                .targetCount(groupBuyOrder.getTargetCount())
                .completeCount(groupBuyOrder.getCompleteCount())
                .lockCount(groupBuyOrder.getLockCount())
                .status(GroupBuyOrderStatusEnumVO.valueOf(groupBuyOrder.getStatus()))
                .validStartTime(groupBuyOrder.getValidStartTime())
                .validEndTime(groupBuyOrder.getValidEndTime())
                .notifyConfigVO(NotifyConfigVO.builder()
                        .notifyType(NotifyTypeEnumVO.valueOf(groupBuyOrder.getNotifyType()))
                        .notifyUrl(groupBuyOrder.getNotifyUrl())
                        // MQ 是固定的
                        .notifyMQ(topic_team_success)
                        .build())
                .build();
    }


    /**
     * 拼团支付成功后的结算逻辑
     * 步骤：
     *   更新明细表状态 → COMPLETE
     *   更新主表 complete_count + 1
     *   当达到 targetCount 时，更新主表状态 → COMPLETE 并写入回调任务
     * 整体包裹在 5000ms 事务内，任何一步失败都会回滚。
     */
    @Transactional(timeout = 5000)
    @Override
    public NotifyTaskEntity settlementMarketPayOrder(GroupBuyTeamSettlementAggregate agg) {

        // ========= 1. 聚合拆分 =========
        UserEntity           user   = agg.getUserEntity();
        GroupBuyTeamEntity   team   = agg.getGroupBuyTeamEntity();
        NotifyConfigVO notifyConfigVO = team.getNotifyConfigVO();
        TradePaySuccessEntity payOK = agg.getTradePaySuccessEntity();

        // ========= 2. 更新拼团订单明细状态 =========
        GroupBuyOrderList req = new GroupBuyOrderList();
        req.setUserId(user.getUserId());
        req.setOutTradeNo(payOK.getOutTradeNo());
        req.setOutTradeTime(payOK.getOutTradeTime());

        //更新订单状态到已完成
        int rows = groupBuyOrderListDao.updateOrderStatus2COMPLETE(req);
        if (rows != 1) {
            throw new AppException(ResponseCode.UPDATE_ZERO);
        }

        // ========= 3. 更新拼团已完成人数 =========
        int addCnt = groupBuyOrderDao.updateAddCompleteCount(team.getTeamId());
        if (addCnt != 1) {
            throw new AppException(ResponseCode.UPDATE_ZERO);
        }

        // ========= 4. 达标后收尾（状态改为 COMPLETE & 写入回调任务） =========
        if (team.getTargetCount() - team.getCompleteCount() == 1) { // 刚好凑齐
            int updStat = groupBuyOrderDao.updateOrderStatus2COMPLETE(team.getTeamId());
            if (updStat != 1) {
                throw new AppException(ResponseCode.UPDATE_ZERO);
            }

            // 查询已完成外部交易号列表，用于业务通知
            List<String> outTradeNoList = groupBuyOrderListDao.queryGroupBuyCompleteOrderOutTradeNoListByTeamId(team.getTeamId());

            // 构造并写入通知任务（异步回调）
            NotifyTask task = new NotifyTask();
            task.setActivityId(team.getActivityId());
            task.setTeamId(team.getTeamId());
            task.setNotifyCategory(TaskNotifyCategoryEnumVO.TRADE_SETTLEMENT.getCode());   //拼团成团消息
            task.setNotifyType(notifyConfigVO.getNotifyType().getCode());                   //HTTP or MQ
            task.setNotifyMQ(NotifyTypeEnumVO.MQ.equals(notifyConfigVO.getNotifyType()) ? notifyConfigVO.getNotifyMQ() : null);
            task.setNotifyUrl(NotifyTypeEnumVO.HTTP.equals(notifyConfigVO.getNotifyType()) ? notifyConfigVO.getNotifyUrl() : null);
            task.setNotifyCount(0);
            task.setNotifyStatus(0);
            task.setUuid(team.getTeamId() + Constants.UNDERLINE + TaskNotifyCategoryEnumVO.TRADE_SETTLEMENT.getCode() + Constants.UNDERLINE + payOK.getOutTradeNo());

            //setParameterJson：回调的消息，返回拼团成功的团队id和外部交易单号list，便于外部系统做后续处理，如发货
            task.setParameterJson(JSON.toJSONString(new HashMap<String, Object>() {{
                put("teamId", team.getTeamId());
                put("outTradeNoList", outTradeNoList);
            }}));

            notifyTaskDao.insert(task);
            return NotifyTaskEntity.builder()
                    .teamId(task.getTeamId())
                    .notifyType(task.getNotifyType())
                    .notifyMQ(task.getNotifyMQ())
                    .notifyUrl(task.getNotifyUrl())
                    .notifyCount(task.getNotifyCount())
                    .parameterJson(task.getParameterJson())
                    .build();
        }
        return null;
    }

    /**
     * 渠道是否被拦截
     * @param source
     * @param channel
     * @return
     */
    @Override
    public boolean isSCBlackIntercept(String source, String channel) {
        return dccService.isSCBlackIntercept(source, channel);
    }

    /**
     * 查询 所有 未执行或待重试的通知任务
     * @return 如果不存在相关任务，则返回空列表；否则返回实体列表
     */
    @Override
    public List<NotifyTaskEntity> queryUnExecutedNotifyTaskList() {
        List<NotifyTask> notifyTaskList = notifyTaskDao.queryUnExecutedNotifyTaskList();
        if (CollectionUtils.isEmpty(notifyTaskList)) {
            return Collections.emptyList();
        }

        List<NotifyTaskEntity> notifyTaskEntities = new ArrayList<>();
        for (NotifyTask notifyTask : notifyTaskList) {

            NotifyTaskEntity notifyTaskEntity = new NotifyTaskEntity();
            // 将同名属性从 task 拷贝到 entity
            BeanUtils.copyProperties(notifyTask, notifyTaskEntity);
            notifyTaskEntities.add(notifyTaskEntity);
        }

        return notifyTaskEntities;
    }

    /**
     * 根据指定 teamId 查询未执行或待重试的单个通知任务
     * @param teamId 拼团团队标识
     * @return 返回长度为 0 或 1 的列表，封装 NotifyTaskEntity
     */
    @Override
    public List<NotifyTaskEntity> queryUnExecutedNotifyTaskList(String teamId) {
        NotifyTask notifyTask = notifyTaskDao.queryUnExecutedNotifyTaskByTeamId(teamId);
        if (null == notifyTask) return new ArrayList<>();
        return Collections.singletonList(NotifyTaskEntity.builder()
                .teamId(notifyTask.getTeamId())
                .notifyType(notifyTask.getNotifyType())
                .notifyMQ(notifyTask.getNotifyMQ())
                .notifyUrl(notifyTask.getNotifyUrl())
                .notifyCount(notifyTask.getNotifyCount())
                .parameterJson(notifyTask.getParameterJson())
                .uuid(notifyTask.getUuid())
                .build());
    }

    /**
     * 更新指定 teamId 的通知任务状态为成功
     */
    @Override
    public int updateNotifyTaskStatusSuccess(NotifyTaskEntity notifyTaskEntity) {
        NotifyTask notifyTask = NotifyTask.builder()
                .teamId(notifyTaskEntity.getTeamId())
                .uuid(notifyTaskEntity.getUuid())
                .build();
        return notifyTaskDao.updateNotifyTaskStatusSuccess(notifyTask);
    }

    /**
     * 更新指定 teamId 的通知任务状态为失败（不可重试）
     */
    @Override
    public int updateNotifyTaskStatusError(NotifyTaskEntity notifyTaskEntity) {
        NotifyTask notifyTask = NotifyTask.builder()
                .teamId(notifyTaskEntity.getTeamId())
                .uuid(notifyTaskEntity.getUuid())
                .build();
        return notifyTaskDao.updateNotifyTaskStatusError(notifyTask);
    }

    /**
     * 更新指定 teamId 的通知任务状态为重试
     */
    @Override
    public int updateNotifyTaskStatusRetry(NotifyTaskEntity notifyTaskEntity) {
        NotifyTask notifyTask = NotifyTask.builder()
                .teamId(notifyTaskEntity.getTeamId())
                .uuid(notifyTaskEntity.getUuid())
                .build();
        return notifyTaskDao.updateNotifyTaskStatusRetry(notifyTask);
    }

    /**
     * 占用库存，防止超卖
     */
    @Override
    public boolean occupyTeamStock(String teamOccupiedStockKey, String recoveryTeamStockKey, Integer target, Integer validTime) {
        // 获取失败恢复量
        Long recoveryCount = redisService.getAtomicLong(recoveryTeamStockKey);
        recoveryCount = null == recoveryCount ? 0 : recoveryCount;

        // 1. incr 得到值，与总量和恢复量做对比。恢复量为系统失败时候记录的量。
        // 2. 从有组队量开始，相当于已经有了一个占用量，所以要 +1，因为团长开团的时候teamid为null，但事实上锁单已经有一单了。
        long occupy = redisService.incr(teamOccupiedStockKey) + 1;    //取teamOccupiedStockKey的值，先自增，再返回；类似++i

        if (occupy > target + recoveryCount) {
            return false;
        }


        // 1. 给每个产生的值加锁为兜底设计，虽然incr操作是原子的，基本不会产生一样的值。但在实际生产中，遇到过集群的运维配置问题，以及业务运营配置数据问题，导致incr得到的值相同。
        // 2. validTime + 60分钟，是一个延后时间的设计，让数据保留时间稍微长一些，便于排查问题。
        String lockKey = teamOccupiedStockKey + Constants.UNDERLINE + occupy;
        Boolean lock = redisService.setNx(lockKey, validTime + 60, TimeUnit.MINUTES);

        if (!lock) {
            log.info("组队库存加锁失败 {}", lockKey);
        }

        return lock;
    }

    /**
     * 恢复库存
     */
    @Override
    public void recoveryTeamStock(String recoveryteamOccupiedStockKey, Integer validTime) {
        // 首次组队拼团，是没有 teamId 的，所以不需要这个做处理。
        if (StringUtils.isBlank(recoveryteamOccupiedStockKey)) return;

        redisService.incr(recoveryteamOccupiedStockKey);
    }

    /**
     * 用户未支付的退单，在一个事务内完成，因为要同时修改两张表
     */
    @Override
    @Transactional(timeout = 5000)
    public NotifyTaskEntity unpaid2Refund(GroupBuyRefundAggregate groupBuyRefundAggregate) {
        TradeRefundOrderEntity tradeRefundOrderEntity = groupBuyRefundAggregate.getTradeRefundOrderEntity();
        GroupBuyProgressVO groupBuyProgress = groupBuyRefundAggregate.getGroupBuyProgress();

        GroupBuyOrderList groupBuyOrderListReq = new GroupBuyOrderList();
        // 保留userId，企业中往往会根据 userId 作为分库分表路由键，如果将来做分库分表也可以方便处理
        groupBuyOrderListReq.setUserId(tradeRefundOrderEntity.getUserId());
        groupBuyOrderListReq.setOrderId(tradeRefundOrderEntity.getOrderId());

        int updateUnpaid2RefundCount = groupBuyOrderListDao.unpaid2Refund(groupBuyOrderListReq);
        if (1 != updateUnpaid2RefundCount) {
            log.error("逆向流程，更新订单状态(退单)失败 {} {}", tradeRefundOrderEntity.getUserId(), tradeRefundOrderEntity.getOrderId());
            throw new AppException(ResponseCode.UPDATE_ZERO);
        }

        GroupBuyOrder groupBuyOrderReq = new GroupBuyOrder();
        groupBuyOrderReq.setTeamId(tradeRefundOrderEntity.getTeamId());
        groupBuyOrderReq.setLockCount(groupBuyProgress.getLockCount());

        int updateTeamUnpaid2Refund = groupBuyOrderDao.unpaid2Refund(groupBuyOrderReq);
        if (1 != updateTeamUnpaid2Refund) {
            log.error("逆向流程，更新组队记录(退单)失败 {} {}", tradeRefundOrderEntity.getUserId(), tradeRefundOrderEntity.getOrderId());
            throw new AppException(ResponseCode.UPDATE_ZERO);
        }

        // 本地消息任务表
        NotifyTask notifyTask = new NotifyTask();
        notifyTask.setActivityId(tradeRefundOrderEntity.getActivityId());
        notifyTask.setTeamId(tradeRefundOrderEntity.getTeamId());
        notifyTask.setNotifyCategory(TaskNotifyCategoryEnumVO.TRADE_UNPAID2REFUND.getCode());
        notifyTask.setNotifyType(NotifyTypeEnumVO.MQ.getCode());
        notifyTask.setNotifyMQ(topic_team_refund);
        notifyTask.setNotifyCount(0);
        notifyTask.setNotifyStatus(0);
        notifyTask.setUuid(tradeRefundOrderEntity.getTeamId() + Constants.UNDERLINE + TaskNotifyCategoryEnumVO.TRADE_UNPAID2REFUND.getCode() + Constants.UNDERLINE + tradeRefundOrderEntity.getOrderId());

        notifyTask.setParameterJson(JSON.toJSONString(new HashMap<String, Object>() {{
            put("type", RefundTypeEnumVO.UNPAID_UNLOCK.getCode());
            put("userId", tradeRefundOrderEntity.getUserId());
            put("teamId", tradeRefundOrderEntity.getTeamId());
            put("orderId", tradeRefundOrderEntity.getOrderId());
            put("outTradeNo", tradeRefundOrderEntity.getOutTradeNo());
            put("activityId", tradeRefundOrderEntity.getActivityId());
        }}));

        notifyTaskDao.insert(notifyTask);

        return NotifyTaskEntity.builder()
                .teamId(notifyTask.getTeamId())
                .notifyType(notifyTask.getNotifyType())
                .notifyMQ(notifyTask.getNotifyMQ())
                .notifyCount(notifyTask.getNotifyCount())
                .parameterJson(notifyTask.getParameterJson())
                .uuid(notifyTask.getUuid())
                .build();
    }

    /**
     * 用户已支付后退单
     */
    @Override
    @Transactional(timeout = 5000)
    public NotifyTaskEntity paid2Refund(GroupBuyRefundAggregate groupBuyRefundAggregate) {
        TradeRefundOrderEntity tradeRefundOrderEntity = groupBuyRefundAggregate.getTradeRefundOrderEntity();
        GroupBuyProgressVO groupBuyProgress = groupBuyRefundAggregate.getGroupBuyProgress();

        GroupBuyOrderList groupBuyOrderListReq = new GroupBuyOrderList();
        // 保留userId，企业中往往会根据 userId 作为分库分表路由键，如果将来做分库分表也可以方便处理
        groupBuyOrderListReq.setUserId(tradeRefundOrderEntity.getUserId());
        groupBuyOrderListReq.setOrderId(tradeRefundOrderEntity.getOrderId());

        // 更新订单明细表
        int updatePaid2RefundCount = groupBuyOrderListDao.paid2Refund(groupBuyOrderListReq);
        if (1 != updatePaid2RefundCount) {
            log.error("逆向流程-paid2Refund，更新订单状态(退单)失败 {} {}", tradeRefundOrderEntity.getUserId(), tradeRefundOrderEntity.getOrderId());
            throw new AppException(ResponseCode.UPDATE_ZERO);
        }

        GroupBuyOrder groupBuyOrderReq = new GroupBuyOrder();
        groupBuyOrderReq.setTeamId(tradeRefundOrderEntity.getTeamId());
        groupBuyOrderReq.setLockCount(groupBuyProgress.getLockCount());
        groupBuyOrderReq.setCompleteCount(groupBuyProgress.getCompleteCount());

        int updateTeamPaid2Refund = groupBuyOrderDao.paid2Refund(groupBuyOrderReq);
        if (1 != updateTeamPaid2Refund) {
            log.error("逆向流程-paid2Refund，更新组队记录(退单)失败 {} {}", tradeRefundOrderEntity.getUserId(), tradeRefundOrderEntity.getOrderId());
            throw new AppException(ResponseCode.UPDATE_ZERO);
        }

        // 本地消息任务表
        NotifyTask notifyTask = new NotifyTask();
        notifyTask.setActivityId(tradeRefundOrderEntity.getActivityId());
        notifyTask.setTeamId(tradeRefundOrderEntity.getTeamId());
        notifyTask.setNotifyCategory(TaskNotifyCategoryEnumVO.TRADE_PAID2REFUND.getCode());
        notifyTask.setNotifyType(NotifyTypeEnumVO.MQ.getCode());
        notifyTask.setNotifyMQ(topic_team_refund);
        notifyTask.setNotifyCount(0);
        notifyTask.setNotifyStatus(0);
        notifyTask.setUuid(tradeRefundOrderEntity.getTeamId() + Constants.UNDERLINE + TaskNotifyCategoryEnumVO.TRADE_PAID2REFUND.getCode() + Constants.UNDERLINE + tradeRefundOrderEntity.getOrderId());


        //具体消息内容
        notifyTask.setParameterJson(JSON.toJSONString(new HashMap<String, Object>() {{
            put("type", RefundTypeEnumVO.PAID_UNFORMED.getCode());
            put("userId", tradeRefundOrderEntity.getUserId());
            put("teamId", tradeRefundOrderEntity.getTeamId());
            put("orderId", tradeRefundOrderEntity.getOrderId());
            put("outTradeNo", tradeRefundOrderEntity.getOutTradeNo());
            put("activityId", tradeRefundOrderEntity.getActivityId());
        }}));

        //插入到任务表，若执行失败后可以补偿
        notifyTaskDao.insert(notifyTask);

        return NotifyTaskEntity.builder()
                .teamId(notifyTask.getTeamId())
                .notifyType(notifyTask.getNotifyType())
                .notifyMQ(notifyTask.getNotifyMQ())
                .notifyCount(notifyTask.getNotifyCount())
                .parameterJson(notifyTask.getParameterJson())
                .uuid(notifyTask.getUuid())
                .build();
    }

    /**
     * 已支付已成团后退单
     */
    @Override
    @Transactional(timeout = 5000)
    public NotifyTaskEntity paidTeam2Refund(GroupBuyRefundAggregate groupBuyRefundAggregate) {
        TradeRefundOrderEntity tradeRefundOrderEntity = groupBuyRefundAggregate.getTradeRefundOrderEntity();
        GroupBuyProgressVO groupBuyProgress = groupBuyRefundAggregate.getGroupBuyProgress();
        GroupBuyOrderStatusEnumVO groupBuyOrderEnumVO = groupBuyRefundAggregate.getGroupBuyOrderEnumVO();

        GroupBuyOrderList groupBuyOrderListReq = new GroupBuyOrderList();
        // 保留userId，企业中往往会根据 userId 作为分库分表路由键，如果将来做分库分表也可以方便处理
        groupBuyOrderListReq.setUserId(tradeRefundOrderEntity.getUserId());
        groupBuyOrderListReq.setOrderId(tradeRefundOrderEntity.getOrderId());

        int updatePaid2RefundCount = groupBuyOrderListDao.paidTeam2Refund(groupBuyOrderListReq);
        if (1 != updatePaid2RefundCount) {
            log.error("逆向流程-paidTeam2Refund，更新订单状态(退单)失败 {} {}", tradeRefundOrderEntity.getUserId(), tradeRefundOrderEntity.getOrderId());
            throw new AppException(ResponseCode.UPDATE_ZERO);
        }

        GroupBuyOrder groupBuyOrderReq = new GroupBuyOrder();
        groupBuyOrderReq.setTeamId(tradeRefundOrderEntity.getTeamId());
        groupBuyOrderReq.setLockCount(groupBuyProgress.getLockCount());
        groupBuyOrderReq.setCompleteCount(groupBuyProgress.getCompleteCount());

        // 根据拼团组队量更新状态。组队最后一个人->更新组队失败，组队还有其他人->更新组队完成含退单
        if (GroupBuyOrderStatusEnumVO.COMPLETE_FAIL.equals(groupBuyOrderEnumVO)) {
            int updateTeamPaid2Refund = groupBuyOrderDao.paidTeam2Refund(groupBuyOrderReq);
            if (1 != updateTeamPaid2Refund) {
                log.error("逆向流程-paidTeam2Refund，更新组队记录(退单)失败 {} {}", tradeRefundOrderEntity.getUserId(), tradeRefundOrderEntity.getOrderId());
                throw new AppException(ResponseCode.UPDATE_ZERO);
            }
        } else if (GroupBuyOrderStatusEnumVO.FAIL.equals(groupBuyOrderEnumVO)){
            int updateTeamPaid2RefundFail = groupBuyOrderDao.paidTeam2RefundFail(groupBuyOrderReq);
            if (1 != updateTeamPaid2RefundFail) {
                log.error("逆向流程-updateTeamPaid2RefundFail，更新组队记录(退单)失败 {} {}", tradeRefundOrderEntity.getUserId(), tradeRefundOrderEntity.getOrderId());
                throw new AppException(ResponseCode.UPDATE_ZERO);
            }
        }

        // 本地消息任务表
        NotifyTask notifyTask = new NotifyTask();
        notifyTask.setActivityId(tradeRefundOrderEntity.getActivityId());
        notifyTask.setTeamId(tradeRefundOrderEntity.getTeamId());
        notifyTask.setNotifyCategory(TaskNotifyCategoryEnumVO.TRADE_PAID_TEAM2REFUND.getCode());
        notifyTask.setNotifyType(NotifyTypeEnumVO.MQ.getCode());
        notifyTask.setNotifyMQ(topic_team_refund);
        notifyTask.setNotifyCount(0);
        notifyTask.setNotifyStatus(0);
        notifyTask.setUuid(tradeRefundOrderEntity.getTeamId() + Constants.UNDERLINE + TaskNotifyCategoryEnumVO.TRADE_PAID_TEAM2REFUND.getCode() + Constants.UNDERLINE + tradeRefundOrderEntity.getOrderId());

        notifyTask.setParameterJson(JSON.toJSONString(new HashMap<String, Object>() {{
            put("type", RefundTypeEnumVO.PAID_FORMED.getCode());
            put("userId", tradeRefundOrderEntity.getUserId());
            put("teamId", tradeRefundOrderEntity.getTeamId());
            put("orderId", tradeRefundOrderEntity.getOrderId());
            put("outTradeNo", tradeRefundOrderEntity.getOutTradeNo());
            put("activityId", tradeRefundOrderEntity.getActivityId());
        }}));

        notifyTaskDao.insert(notifyTask);

        return NotifyTaskEntity.builder()
                .teamId(notifyTask.getTeamId())
                .notifyType(notifyTask.getNotifyType())
                .notifyMQ(notifyTask.getNotifyMQ())
                .notifyCount(notifyTask.getNotifyCount())
                .parameterJson(notifyTask.getParameterJson())
                .build();
    }

    @Override
    public void refund2AddRecovery(String recoveryTeamStockKey, String orderId) {
        // 如果恢复库存key为空，直接返回
        if (StringUtils.isBlank(recoveryTeamStockKey) || StringUtils.isBlank(orderId)) {
            return;
        }

        // 使用orderId作为锁的key，避免同一订单重复恢复库存
        String lockKey = "refund_lock_" + orderId;

        // 尝试获取分布式锁，防止重复操作 7天过期
        Boolean lockAcquired = redisService.setNx(lockKey, 7 * 24 * 60 * 60 , TimeUnit.MINUTES);

        if (!lockAcquired) {
            log.warn("订单 {} 恢复库存操作已在进行中，跳过重复操作", orderId);
            return;
        }

        try {
            // 在锁保护下执行库存恢复操作
            redisService.incr(recoveryTeamStockKey);
            log.info("订单 {} 恢复库存成功，恢复库存key: {}", orderId, recoveryTeamStockKey);
        } catch (Exception e) {
            log.error("订单 {} 恢复库存失败，恢复库存key: {}", orderId, recoveryTeamStockKey, e);
            // 如果抛异常则释放锁，允许MQ重新消费恢复库存
            redisService.remove(lockKey);
            throw e;
        }

    }


    @Override
    public List<UserGroupBuyOrderDetailEntity> queryTimeoutUnpaidOrderList() {
        // 1. 查询所有超时未支付的拼团订单列表
        List<GroupBuyOrderList> groupBuyOrderLists = groupBuyOrderListDao.queryTimeoutUnpaidOrderList();

        // 空值保护：如果查询结果为空，直接返回空集合
        if (CollectionUtils.isEmpty(groupBuyOrderLists)) {
            return Collections.emptyList();
        }

        // 2. 提取所有团队ID（去重）
        Set<String> teamIds = groupBuyOrderLists.stream()
                .map(GroupBuyOrderList::getTeamId)
                .collect(Collectors.toSet());

        // 3. 根据团队ID批量查询拼团团队信息
        List<GroupBuyOrder> groupBuyOrders = groupBuyOrderDao.queryGroupBuyTeamByTeamIds(teamIds);

        // 空值保护：如果团队信息为空，直接返回空集合
        if (CollectionUtils.isEmpty(groupBuyOrders)) {
            return Collections.emptyList();
        }

        // 4. 构建团队信息映射表（teamId -> GroupBuyOrder）
        Map<String, GroupBuyOrder> groupBuyOrderMap = groupBuyOrders.stream()
                .collect(Collectors.toMap(
                        GroupBuyOrder::getTeamId,
                        order -> order,
                        (existing, replacement) -> existing // 如果有重复key，保留已存在的
                ));

        // 5. 数据转换：合并订单信息和团队信息
        List<UserGroupBuyOrderDetailEntity> result = new ArrayList<>();
        for (GroupBuyOrderList order : groupBuyOrderLists) {
            // 5.1 获取对应团队信息
            GroupBuyOrder teamInfo = groupBuyOrderMap.get(order.getTeamId());

            // 团队不存在则跳过当前订单
            if (teamInfo == null) {
                continue;
            }

            // 5.2 构建组合实体对象
            UserGroupBuyOrderDetailEntity entity = UserGroupBuyOrderDetailEntity.builder()
                    .userId(order.getUserId())
                    .teamId(teamInfo.getTeamId())
                    .activityId(teamInfo.getActivityId())
                    .targetCount(teamInfo.getTargetCount())
                    .completeCount(teamInfo.getCompleteCount())
                    .lockCount(teamInfo.getLockCount())
                    .validStartTime(teamInfo.getValidStartTime())
                    .validEndTime(teamInfo.getValidEndTime())
                    .outTradeNo(order.getOutTradeNo())
                    .source(order.getSource())
                    .channel(order.getChannel())
                    .build();

            result.add(entity);
        }

        return result;
    }

}
