package com.starry.admin.modules.order.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.common.exception.ServiceException;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
import com.starry.admin.modules.order.mapper.PlayOrderLogInfoMapper;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.constant.OrderConstant.BalanceOperationType;
import com.starry.admin.modules.order.module.constant.OrderConstant.OperatorType;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderActor;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderRefundFlag;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderRefundRecordType;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderRefundState;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderSettlementState;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderStatus;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderTriggerSource;
import com.starry.admin.modules.order.module.constant.OrderConstant.OrdersExpiredState;
import com.starry.admin.modules.order.module.constant.OrderConstant.PayMethod;
import com.starry.admin.modules.order.module.constant.OrderConstant.PaymentSource;
import com.starry.admin.modules.order.module.constant.OrderConstant.PlaceType;
import com.starry.admin.modules.order.module.constant.OrderConstant.ReviewRequirement;
import com.starry.admin.modules.order.module.constant.OrderConstant.YesNoFlag;
import com.starry.admin.modules.order.module.dto.CommodityInfo;
import com.starry.admin.modules.order.module.dto.OrderAmountBreakdown;
import com.starry.admin.modules.order.module.dto.OrderCompletionContext;
import com.starry.admin.modules.order.module.dto.OrderCreationContext;
import com.starry.admin.modules.order.module.dto.OrderPlacementCommand;
import com.starry.admin.modules.order.module.dto.OrderPlacementResult;
import com.starry.admin.modules.order.module.dto.OrderRefundContext;
import com.starry.admin.modules.order.module.dto.OrderRevocationContext;
import com.starry.admin.modules.order.module.dto.PaymentInfo;
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderLogInfoEntity;
import com.starry.admin.modules.order.module.event.OrderRevocationEvent;
import com.starry.admin.modules.order.module.vo.ClerkEstimatedRevenueVo;
import com.starry.admin.modules.order.service.IOrderLifecycleService;
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
import com.starry.admin.modules.order.service.support.ClerkRevenueCalculator;
import com.starry.admin.modules.personnel.service.IPlayBalanceDetailsInfoService;
import com.starry.admin.modules.shop.module.constant.CouponUseState;
import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity;
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
import com.starry.admin.modules.shop.module.enums.CouponDiscountType;
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
import com.starry.admin.modules.weichat.service.NotificationSender;
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
import com.starry.admin.modules.withdraw.service.IEarningsService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
public class OrderLifecycleServiceImpl implements IOrderLifecycleService {

    private static final LifecycleToken LIFECYCLE_TOKEN = new LifecycleToken();
    private static final int MONEY_SCALE = 2;

    private enum LifecycleOperation {
        CREATE,
        COMPLETE,
        REFUND,
        REVOKE_COMPLETED
    }

    @Resource
    private PlayOrderInfoMapper orderInfoMapper;

    @Resource
    private IEarningsService earningsService;

    @Resource
    private NotificationSender notificationSender;

    @Resource
    private IPlayOrderRefundInfoService orderRefundInfoService;

    @Resource
    private IPlayCustomUserInfoService customUserInfoService;

    @Resource
    private IPlayCouponDetailsService playCouponDetailsService;

    @Resource
    private IPlayCouponInfoService playCouponInfoService;

    @Resource
    private ClerkRevenueCalculator clerkRevenueCalculator;

    @Resource
    private PlayOrderLogInfoMapper orderLogInfoMapper;

    @Resource
    private IPlayBalanceDetailsInfoService playBalanceDetailsInfoService;

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    private Map<StrategyKey, OrderPlacementStrategy> placementStrategies;

    @PostConstruct
    void initPlacementStrategies() {
        Map<StrategyKey, OrderPlacementStrategy> strategies = new HashMap<>();

        OrderPlacementStrategy specifiedStrategy = new ServiceOrderPlacementStrategy(this, OrderConstant.PlaceType.SPECIFIED);
        registerStrategy(strategies, OrderConstant.PlaceType.SPECIFIED, OrderConstant.RewardType.NOT_APPLICABLE, specifiedStrategy);

        OrderPlacementStrategy randomStrategy = new ServiceOrderPlacementStrategy(this, OrderConstant.PlaceType.RANDOM);
        registerStrategy(strategies, OrderConstant.PlaceType.RANDOM, OrderConstant.RewardType.NOT_APPLICABLE, randomStrategy);

        OrderPlacementStrategy otherStrategy = new OtherOrderPlacementStrategy(this);
        registerStrategy(strategies, OrderConstant.PlaceType.OTHER, OrderConstant.RewardType.NOT_APPLICABLE, otherStrategy);

        OrderPlacementStrategy rewardGiftStrategy = new RewardGiftOrderPlacementStrategy(this);
        registerStrategy(strategies, OrderConstant.PlaceType.REWARD, OrderConstant.RewardType.GIFT, rewardGiftStrategy);

        OrderPlacementStrategy rewardTipStrategy = new RewardTipOrderPlacementStrategy(this);
        registerStrategy(strategies, OrderConstant.PlaceType.REWARD, OrderConstant.RewardType.BALANCE, rewardTipStrategy);
        registerStrategy(strategies, OrderConstant.PlaceType.REWARD, OrderConstant.RewardType.NOT_APPLICABLE, rewardTipStrategy);

        this.placementStrategies = strategies;
    }

    private void registerStrategy(
            Map<StrategyKey, OrderPlacementStrategy> strategies,
            OrderConstant.PlaceType placeType,
            OrderConstant.RewardType rewardType,
            OrderPlacementStrategy strategy) {
        StrategyKey key = StrategyKey.of(placeType, rewardType);
        if (strategies.containsKey(key)) {
            throw new IllegalStateException(String.format("Duplicate order placement strategy for %s/%s", placeType, rewardType));
        }
        strategies.put(key, strategy);
    }

    private boolean isRewardScenario(OrderCreationContext context) {
        return context.getPlaceType() == OrderConstant.PlaceType.REWARD;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public OrderPlacementResult placeOrder(OrderPlacementCommand command) {
        if (command == null || command.getOrderContext() == null) {
            throw new CustomException("下单参数不能为空");
        }

        OrderCreationContext context = command.getOrderContext();
        OrderConstant.PlaceType placeType = context.getPlaceType();
        log.info("placeOrder placeType={} rewardFlag? {}", placeType, isRewardScenario(context));
        if (placeType == null) {
            throw new CustomException("下单类型不能为空");
        }

        validateCouponUsage(context);


        OrderConstant.RewardType rewardType = context.getRewardType() != null
                ? context.getRewardType()
                : OrderConstant.RewardType.NOT_APPLICABLE;
        OrderPlacementStrategy strategy = placementStrategies.get(StrategyKey.of(placeType, rewardType));
        if (strategy == null && rewardType != OrderConstant.RewardType.NOT_APPLICABLE) {
            strategy = placementStrategies.get(StrategyKey.of(placeType, OrderConstant.RewardType.NOT_APPLICABLE));
        }
        if (strategy == null) {
            throw new CustomException("暂不支持该类型的下单逻辑");
        }
        return strategy.place(command);
    }

    PlayOrderInfoEntity createOrderRecord(OrderCreationContext context) {
        validateOrderCreationRequest(context);

        PlayOrderInfoEntity entity = buildOrderEntity(context);
        applyRandomOrderRequirements(entity, context.getRandomOrderRequirements());
        applyAcceptByInfo(entity, context);
        boolean rewardScenario = isRewardScenario(context);
        if (rewardScenario) {
            applyRewardOrderDefaults(entity);
        }

        customUserInfoService.saveOrderInfo(entity);
        orderInfoMapper.insert(entity);
        String creationOperationType = resolveCreationOperationType(context.getCreatorActor());
        recordOrderLog(
                entity,
                context.getCreatorActor(),
                context.getCreatorId(),
                LifecycleOperation.CREATE,
                context.getRemark(),
                creationOperationType);
        updateCouponUsage(context.getPaymentInfo().getCouponIds());

        if (rewardScenario && StrUtil.isNotBlank(context.getAcceptBy())) {
            completeOrder(
                    entity.getId(),
                    OrderCompletionContext.of(OrderActor.SYSTEM, null, OrderTriggerSource.REWARD_ORDER)
                            .withForceNotify(true)
                            .withComment("auto-complete reward order"));
        }
        return entity;
    }

    boolean shouldDeductBalance(OrderCreationContext context) {
        if (context == null) {
            return true;
        }
        OrderConstant.PaymentSource paymentSource = context.resolvePaymentSource();
        return paymentSource != OrderConstant.PaymentSource.BLIND_BOX;
    }

    OrderAmountBreakdown calculateOrderAmounts(OrderPlacementCommand.PricingInput pricingInput) {
        int quantity = pricingInput.getQuantity();
        if (quantity <= 0) {
            throw new CustomException("数量不能为空");
        }
        BigDecimal normalizedUnitPrice = normalizeMoney(pricingInput.getUnitPrice());
        BigDecimal grossAmount = normalizeMoney(normalizedUnitPrice.multiply(BigDecimal.valueOf(quantity)));

        List<String> couponIds = pricingInput.getCouponIds();
        if (CollectionUtil.isEmpty(couponIds)) {
            return OrderAmountBreakdown.of(grossAmount, BigDecimal.ZERO, grossAmount);
        }

        BigDecimal discountTotal = BigDecimal.ZERO;
        for (String couponId : couponIds) {
            PlayCouponDetailsEntity couponDetails = playCouponDetailsService.getById(couponId);
            if (couponDetails == null) {
                throw new CustomException("优惠券不存在");
            }
            PlayCouponInfoEntity couponInfo = playCouponInfoService.selectPlayCouponInfoById(couponDetails.getCouponId());
            playCouponInfoService.getCouponDetailRestrictionReason(
                    couponInfo,
                    couponDetails.getUseState(),
                    couponDetails.getObtainingTime())
                    .ifPresent(reason -> {
                        throw new CustomException("优惠券不可用 - " + reason);
                    });
            String reason = playCouponInfoService.getCouponReasonForUnavailableUse(
                    couponInfo,
                    pricingInput.getPlaceType().getCode(),
                    pricingInput.getCommodityId(),
                    quantity,
                    normalizedUnitPrice);
            if (StrUtil.isNotBlank(reason)) {
                throw new CustomException("优惠券不可用 - " + reason);
            }

            CouponDiscountType discountType;
            try {
                discountType = CouponDiscountType.fromCode(couponInfo.getDiscountType());
            } catch (CustomException ex) {
                log.warn("Unsupported coupon discount type {}, couponId={}", couponInfo.getDiscountType(), couponInfo.getId());
                continue;
            }

            switch (discountType) {
                case FULL_REDUCTION:
                    discountTotal = discountTotal.add(normalizeMoney(couponInfo.getDiscountAmount()));
                    break;
                case PERCENTAGE:
                    discountTotal = discountTotal.add(calculatePercentageDiscount(grossAmount, couponInfo.getDiscountAmount(), couponInfo.getId()));
                    break;
                default:
                    break;
            }
        }

        BigDecimal discountAmount = normalizeMoney(discountTotal.min(grossAmount));
        BigDecimal netAmount = normalizeMoney(grossAmount.subtract(discountAmount));
        return OrderAmountBreakdown.of(grossAmount, discountAmount, netAmount);
    }

    void validateSufficientBalance(String customerId, BigDecimal requiredAmount) {
        PlayCustomUserInfoEntity customer = customUserInfoService.selectById(customerId);
        if (customer == null) {
            throw new CustomException("顾客不存在");
        }
        BigDecimal before = normalizeMoney(customer.getAccountBalance());
        BigDecimal required = normalizeMoney(requiredAmount);
        if (required.compareTo(before) > 0) {
            throw new ServiceException("余额不足", 998);
        }
    }

    void deductCustomerBalance(
            String customerId,
            BigDecimal netAmount,
            String operationAction,
            String orderId) {
        PlayCustomUserInfoEntity customer = customUserInfoService.selectById(customerId);
        if (customer == null) {
            throw new CustomException("顾客不存在");
        }
        BigDecimal before = normalizeMoney(customer.getAccountBalance());
        BigDecimal amountToDeduct = normalizeMoney(netAmount);
        if (amountToDeduct.compareTo(before) > 0) {
            throw new ServiceException("余额不足", 998);
        }
        BigDecimal after = normalizeMoney(before.subtract(amountToDeduct));
        String action = StrUtil.isNotBlank(operationAction) ? operationAction : "下单";
        customUserInfoService.updateAccountBalanceById(
                customerId,
                before,
                after,
                BalanceOperationType.CONSUME.getCode(),
                action,
                amountToDeduct,
                BigDecimal.ZERO,
                orderId);
    }

    OrderAmountBreakdown fallbackBreakdown(PaymentInfo paymentInfo) {
        return OrderAmountBreakdown.of(
                normalizeMoney(paymentInfo.getOrderMoney()),
                normalizeMoney(paymentInfo.getDiscountAmount()),
                normalizeMoney(paymentInfo.getFinalAmount()));
    }

    private static final class StrategyKey {

        private final OrderConstant.PlaceType placeType;
        private final OrderConstant.RewardType rewardType;

        private StrategyKey(OrderConstant.PlaceType placeType, OrderConstant.RewardType rewardType) {
            this.placeType = placeType;
            this.rewardType = rewardType;
        }

        static StrategyKey of(OrderConstant.PlaceType placeType, OrderConstant.RewardType rewardType) {
            return new StrategyKey(placeType, rewardType);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof StrategyKey)) {
                return false;
            }
            StrategyKey that = (StrategyKey) o;
            return placeType == that.placeType && rewardType == that.rewardType;
        }

        @Override
        public int hashCode() {
            return Objects.hash(placeType, rewardType);
        }
    }

    BigDecimal calculatePercentageDiscount(BigDecimal grossAmount, BigDecimal discountFold, String couponId) {
        if (discountFold == null) {
            log.warn("Percentage coupon has no discount amount configured, couponId={}", couponId);
            return BigDecimal.ZERO;
        }

        BigDecimal normalizedFold = discountFold.abs();
        // REVIEW THIS: WHY NORMALIZE this if < 10?
        if (normalizedFold.compareTo(BigDecimal.TEN) > 0) {
            return normalizeMoney(grossAmount);
        }
        BigDecimal rate;
        if (normalizedFold.compareTo(BigDecimal.ONE) <= 0) {
            rate = normalizedFold;
        } else if (normalizedFold.compareTo(BigDecimal.TEN) <= 0) {
            rate = normalizedFold.divide(BigDecimal.TEN, MONEY_SCALE + 2, RoundingMode.HALF_UP);
        } else if (normalizedFold.compareTo(BigDecimal.valueOf(100)) <= 0) {
            rate = normalizedFold.divide(BigDecimal.valueOf(100), MONEY_SCALE + 2, RoundingMode.HALF_UP);
        } else {
            log.warn("Percentage coupon rate out of range, couponId={}, value={}", couponId, discountFold);
            return BigDecimal.ZERO;
        }

        if (rate.compareTo(BigDecimal.ZERO) <= 0) {
            log.warn("Percentage coupon rate <= 0, couponId={}", couponId);
            return BigDecimal.ZERO;
        }
        if (rate.compareTo(BigDecimal.ONE) > 0) {
            log.warn("Percentage coupon rate > 1 after normalisation, couponId={}", couponId);
            rate = BigDecimal.ONE;
        }

        BigDecimal discount = grossAmount.multiply(BigDecimal.ONE.subtract(rate));
        return normalizeMoney(discount.max(BigDecimal.ZERO));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void completeOrder(String orderId, OrderCompletionContext context) {
        if (StrUtil.isBlank(orderId)) {
            throw new CustomException("订单ID不能为空");
        }
        PlayOrderInfoEntity order = orderInfoMapper.selectById(orderId);
        if (order == null) {
            throw new CustomException("订单不存在");
        }

        OrderTriggerSource source = context != null && context.getTriggerSource() != null
                ? context.getTriggerSource()
                : OrderTriggerSource.UNKNOWN;
        boolean alreadyCompleted = OrderStatus.COMPLETED.getCode().equals(order.getOrderStatus());
        if (!alreadyCompleted && !OrderStatus.IN_PROGRESS.getCode().equals(order.getOrderStatus())) {
            log.warn("Skip completing order {}, unexpected status {}, source={}", orderId, order.getOrderStatus(), source.getCode());
            throw new CustomException("订单状态异常，无法完成");
        }

        LocalDateTime now = LocalDateTime.now();
        LocalDateTime endTime = order.getOrderEndTime() != null ? order.getOrderEndTime() : now;

        boolean transitioned = false;
        if (!alreadyCompleted) {
            UpdateWrapper<PlayOrderInfoEntity> transitionWrapper = new UpdateWrapper<>();
            transitionWrapper.eq("id", orderId)
                    .eq("order_status", OrderStatus.IN_PROGRESS.getCode())
                    .set("order_status", OrderStatus.COMPLETED.getCode())
                    .set("order_end_time", endTime);
            transitioned = orderInfoMapper.update(null, transitionWrapper) > 0;
            if (!transitioned) {
                PlayOrderInfoEntity refreshed = orderInfoMapper.selectById(orderId);
                if (refreshed == null) {
                    throw new CustomException("订单不存在");
                }
                if (!OrderStatus.COMPLETED.getCode().equals(refreshed.getOrderStatus())) {
                    log.warn("Failed to transition order {} to completed, current status {}", orderId, refreshed.getOrderStatus());
                    throw new CustomException("订单状态异常，无法完成");
                }
                order = refreshed;
                alreadyCompleted = true;
            }
        }

        if (alreadyCompleted && order.getOrderEndTime() == null) {
            UpdateWrapper<PlayOrderInfoEntity> endTimeWrapper = new UpdateWrapper<>();
            endTimeWrapper.eq("id", orderId)
                    .isNull("order_end_time")
                    .set("order_end_time", endTime);
            orderInfoMapper.update(null, endTimeWrapper);
        }

        PlayOrderInfoEntity latest = orderInfoMapper.selectById(orderId);
        if (latest == null) {
            throw new CustomException("订单不存在");
        }

        boolean forceNotify = context != null && context.isForceNotify();
        boolean earningsCreated = false;
        boolean shouldNotify = false;

        boolean completionLogged = hasLifecycleLog(orderId, LifecycleOperation.COMPLETE);
        boolean shouldApplyCompletion = transitioned;
        if (!shouldApplyCompletion && forceNotify) {
            shouldApplyCompletion = !completionLogged;
        }

        if (shouldApplyCompletion) {
            customUserInfoService.handleOrderCompletion(latest);
            earningsCreated = ensureEarnings(latest, source);
            shouldNotify = true;
            OrderActor actor = context != null ? context.getOperatorActor() : OrderActor.SYSTEM;
            String operationType = resolveCompletionOperationType(context, transitioned);
            recordOrderLog(
                    latest,
                    actor,
                    context != null ? context.getOperatorId() : null,
                    LifecycleOperation.COMPLETE,
                    context != null ? context.getComment() : null,
                    operationType);
        } else if (forceNotify) {
            earningsCreated = ensureEarnings(latest, source);
            shouldNotify = earningsCreated || OrderStatus.COMPLETED.getCode().equals(latest.getOrderStatus());
        }

        if (shouldNotify) {
            notificationSender.sendOrderFinishMessageAsync(latest);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void refundOrder(OrderRefundContext context) {
        if (context == null || StrUtil.isBlank(context.getOrderId())) {
            throw new CustomException("订单ID不能为空");
        }
        PlayOrderInfoEntity order = orderInfoMapper.selectById(context.getOrderId());
        if (order == null) {
            throw new CustomException("订单不存在");
        }
        BigDecimal refundAmount = context.getRefundAmount();
        if (refundAmount == null) {
            throw new CustomException("退款金额不能为空");
        }
        if (refundAmount.compareTo(BigDecimal.ZERO) < 0) {
            throw new CustomException("退款金额不能小于0");
        }
        BigDecimal finalAmount = order.getFinalAmount() == null ? BigDecimal.ZERO : order.getFinalAmount();
        if (finalAmount.compareTo(refundAmount) < 0) {
            throw new CustomException("退款金额不能大于支付金额");
        }
        if (OrderStatus.COMPLETED.getCode().equals(order.getOrderStatus())) {
            throw new CustomException("【已完成】的订单无法操作退款");
        }
        if (OrderStatus.CANCELLED.getCode().equals(order.getOrderStatus())) {
            throw new CustomException("【已取消】的订单无法操作退款");
        }
        if (OrderRefundFlag.REFUNDED.getCode().equals(order.getRefundType())) {
            throw new CustomException("每个订单只能退款一次~");
        }

        if (isBalancePaidOrder(order)
                && !playBalanceDetailsInfoService.existsCustomerConsumeRecord(order.getPurchaserBy(), order.getId())) {
            throw new CustomException("订单未发生余额扣款，无法退款");
        }

        UpdateWrapper<PlayOrderInfoEntity> refundUpdate = new UpdateWrapper<>();
        refundUpdate.eq("id", order.getId())
                .eq("refund_type", OrderRefundFlag.NOT_REFUNDED.getCode())
                .set("refund_type", OrderRefundFlag.REFUNDED.getCode())
                .set("refund_amount", refundAmount)
                .set("refund_reason", context.getRefundReason())
                .set("order_status", OrderStatus.CANCELLED.getCode());
        boolean refundApplied = orderInfoMapper.update(null, refundUpdate) > 0;
        if (!refundApplied) {
            PlayOrderInfoEntity latest = orderInfoMapper.selectById(order.getId());
            if (latest != null && OrderRefundFlag.REFUNDED.getCode().equals(latest.getRefundType())) {
                log.info("Refund already processed for order {}, skipping duplicate credit", order.getId());
                return;
            }
            throw new CustomException("订单已处理或状态异常，无法退款");
        }

        PlayCustomUserInfoEntity customUser = customUserInfoService.getById(order.getPurchaserBy());
        if (customUser == null) {
            throw new CustomException("顾客信息不存在");
        }
        BigDecimal currentBalance = customUser.getAccountBalance() == null ? BigDecimal.ZERO : customUser.getAccountBalance();
        BigDecimal refundSourceAmount = refundAmount;
        customUserInfoService.updateAccountBalanceById(
                customUser.getId(),
                currentBalance,
                currentBalance.add(refundSourceAmount),
                BalanceOperationType.REFUND.getCode(),
                "订单退款",
                refundSourceAmount,
                BigDecimal.ZERO,
                order.getId());

        OrderRefundRecordType refundRecordType = finalAmount.compareTo(refundAmount) == 0
                ? OrderRefundRecordType.FULL
                : OrderRefundRecordType.PARTIAL;
        String refundByType = StrUtil.isNotBlank(context.getOperatorType())
                ? context.getOperatorType()
                : OperatorType.ADMIN.getCode();
        String refundById = StrUtil.isNotBlank(context.getOperatorId()) ? context.getOperatorId() : SecurityUtils.getUserId();

        orderRefundInfoService.add(
                order.getId(),
                order.getPurchaserBy(),
                order.getAcceptBy(),
                order.getPayMethod(),
                refundRecordType.getCode(),
                refundAmount,
                context.getRefundReason(),
                refundByType,
                refundById,
                OrderRefundState.PROCESSING.getCode(),
                ReviewRequirement.NOT_REQUIRED.getCode());

        PlayOrderInfoEntity latest = orderInfoMapper.selectById(order.getId());

        String refundOperationType = String.format("%s_%s", LifecycleOperation.REFUND.name(), refundRecordType.name());
        recordOrderLog(
                latest != null ? latest : order,
                resolveCompletionActor(context.getOperatorType()),
                context.getOperatorId(),
                LifecycleOperation.REFUND,
                context.getRefundReason(),
                refundOperationType);
    }

    private boolean isBalancePaidOrder(PlayOrderInfoEntity order) {
        String sourceCode = order.getPaymentSource();
        if (StrUtil.isBlank(sourceCode)) {
            return true;
        }
        try {
            return PaymentSource.fromCode(sourceCode) == PaymentSource.BALANCE;
        } catch (IllegalArgumentException ex) {
            log.warn("Unknown payment source {}, defaulting to balance for refund guard", sourceCode);
            return true;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revokeCompletedOrder(OrderRevocationContext context) {
        if (context == null || StrUtil.isBlank(context.getOrderId())) {
            throw new CustomException("订单ID不能为空");
        }
        PlayOrderInfoEntity order = orderInfoMapper.selectById(context.getOrderId());
        if (order == null) {
            throw new CustomException("订单不存在");
        }
        if (OrderStatus.REVOKED.getCode().equals(order.getOrderStatus())) {
            throw new CustomException("订单已撤销");
        }
        if (!OrderStatus.COMPLETED.getCode().equals(order.getOrderStatus())) {
            throw new CustomException("当前状态无法撤销");
        }
        if (!OrderConstant.OrderType.NORMAL.getCode().equals(order.getOrderType())) {
            throw new CustomException("仅支持撤销普通服务订单");
        }

        String operatorType = StrUtil.isNotBlank(context.getOperatorType()) ? context.getOperatorType() : OperatorType.ADMIN.getCode();
        context.setOperatorType(operatorType);
        String operatorId = StrUtil.isNotBlank(context.getOperatorId()) ? context.getOperatorId() : SecurityUtils.getUserId();
        context.setOperatorId(operatorId);
        if (context.isDeductClerkEarnings()) {
            String targetClerkId = order.getAcceptBy();
            if (StrUtil.isBlank(targetClerkId)) {
                throw new CustomException("未找到可冲销的店员收益账号");
            }
            BigDecimal availableEarnings = Optional.ofNullable(
                            earningsService.getRemainingEarningsForOrder(order.getId(), targetClerkId))
                    .orElse(BigDecimal.ZERO);
            if (availableEarnings.compareTo(BigDecimal.ZERO) <= 0) {
                throw new CustomException("本单店员收益已全部扣回");
            }
            BigDecimal requested = context.getEarningsAdjustAmount();
            if (requested == null || requested.compareTo(BigDecimal.ZERO) <= 0) {
                requested = availableEarnings;
            }
            if (requested.compareTo(availableEarnings) > 0) {
                throw new CustomException("扣回金额不能超过本单收益" + availableEarnings);
            }
            context.setEarningsAdjustAmount(requested);
        } else {
            context.setEarningsAdjustAmount(BigDecimal.ZERO);
        }

        BigDecimal finalAmount = Optional.ofNullable(order.getFinalAmount()).orElse(BigDecimal.ZERO);
        BigDecimal refundAmount = context.getRefundAmount();
        if (refundAmount == null) {
            refundAmount = context.isRefundToCustomer() ? finalAmount : BigDecimal.ZERO;
        }
        if (refundAmount.compareTo(BigDecimal.ZERO) < 0) {
            throw new CustomException("退款金额不能小于0");
        }
        if (refundAmount.compareTo(finalAmount) > 0) {
            throw new CustomException("退款金额不能大于支付金额");
        }
        context.setRefundAmount(refundAmount);

        if (refundAmount.compareTo(BigDecimal.ZERO) > 0 && context.isRefundToCustomer()) {
            if (isBalancePaidOrder(order)
                    && !playBalanceDetailsInfoService.existsCustomerConsumeRecord(order.getPurchaserBy(), order.getId())) {
                throw new CustomException("订单未发生余额扣款，无法退款");
            }
        }

        UpdateWrapper<PlayOrderInfoEntity> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", order.getId())
                .eq("order_status", OrderStatus.COMPLETED.getCode())
                .set("order_status", OrderStatus.REVOKED.getCode())
                .set("order_cancel_time", LocalDateTime.now())
                .set("refund_amount", refundAmount)
                .set("refund_reason", context.getRefundReason());
        if (refundAmount.compareTo(BigDecimal.ZERO) > 0) {
            updateWrapper.set("refund_type", OrderRefundFlag.REFUNDED.getCode());
        }

        boolean updated = orderInfoMapper.update(null, updateWrapper) > 0;
        PlayOrderInfoEntity latest = orderInfoMapper.selectById(order.getId());
        if (!updated && (latest == null || !OrderStatus.REVOKED.getCode().equals(latest.getOrderStatus()))) {
            throw new CustomException("订单状态已变化，无法撤销");
        }
        if (latest == null) {
            latest = order;
            latest.setOrderStatus(OrderStatus.REVOKED.getCode());
        }

        if (refundAmount.compareTo(BigDecimal.ZERO) > 0 && context.isRefundToCustomer()) {
            OrderRefundRecordType recordType = finalAmount.compareTo(refundAmount) == 0
                    ? OrderRefundRecordType.FULL
                    : OrderRefundRecordType.PARTIAL;
            orderRefundInfoService.add(
                    latest.getId(),
                    latest.getPurchaserBy(),
                    latest.getAcceptBy(),
                    latest.getPayMethod(),
                    recordType.getCode(),
                    refundAmount,
                    context.getRefundReason(),
                    context.getOperatorType(),
                    context.getOperatorId(),
                    OrderRefundState.PROCESSING.getCode(),
                    ReviewRequirement.NOT_REQUIRED.getCode());
        }

        OrderActor actor = resolveCompletionActor(context.getOperatorType());
        String operationType = String.format(
                "%s_%s",
                LifecycleOperation.REVOKE_COMPLETED.name(),
                context.isDeductClerkEarnings() ? "DEDUCT" : "KEEP");
        recordOrderLog(latest, actor, context.getOperatorId(), LifecycleOperation.REVOKE_COMPLETED,
                context.getRefundReason(), operationType);

        applicationEventPublisher.publishEvent(new OrderRevocationEvent(context, latest));
    }

    private void validateOrderCreationRequest(OrderCreationContext context) {
        if (context == null) {
            throw new CustomException("订单创建请求不能为空");
        }
        if (context.getPlaceType() == PlaceType.RANDOM && !context.isValidForRandomOrder()) {
            throw new CustomException("随机单必须提供店员要求信息");
        }
    }

    private void validateCouponUsage(OrderCreationContext context) {
        if (context == null) {
            return;
        }
        PaymentInfo paymentInfo = context.getPaymentInfo();
        if (paymentInfo == null || CollectionUtil.isEmpty(paymentInfo.getCouponIds())) {
            return;
        }
        boolean isBlindBoxPurchase = context.getOrderType() == OrderConstant.OrderType.BLIND_BOX_PURCHASE;
        boolean isBlindBoxDispatch = context.resolvePaymentSource() == OrderConstant.PaymentSource.BLIND_BOX
                || StrUtil.isNotBlank(context.getSourceRewardId());
        boolean isRewardOrder = context.getPlaceType() == OrderConstant.PlaceType.REWARD
                || context.getRewardType() == OrderConstant.RewardType.GIFT
                || context.getRewardType() == OrderConstant.RewardType.BALANCE;
        if (isBlindBoxPurchase || isBlindBoxDispatch || isRewardOrder) {
            throw new CustomException("盲盒/礼物订单暂不支持使用优惠券");
        }
    }

    private PlayOrderInfoEntity buildOrderEntity(OrderCreationContext context) {
        PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
        entity.setId(context.getOrderId());
        entity.setOrderNo(context.getOrderNo());
        entity.updateOrderStatus(LIFECYCLE_TOKEN, context.getOrderStatus());
        entity.setOrderType(context.getOrderType().getCode());
        entity.setPlaceType(context.getPlaceType().getCode());
        entity.setRewardType(context.getRewardType().getCode());
        entity.setFirstOrder(resolveFirstOrderFlag(context));
        entity.setRefundType(OrderRefundFlag.NOT_REFUNDED.getCode());
        entity.setBackendEntry(YesNoFlag.NO.getCode());
        entity.setOrderSettlementState(OrderSettlementState.NOT_SETTLED.getCode());
        entity.setOrdersExpiredState(OrdersExpiredState.NOT_EXPIRED.getCode());

        CommodityInfo commodityInfo = context.getCommodityInfo();
        entity.setCommodityId(commodityInfo.getCommodityId());
        entity.setCommodityType(commodityInfo.getCommodityType().getCode());
        entity.setCommodityPrice(commodityInfo.getCommodityPrice());
        entity.setServiceDuration(commodityInfo.getServiceDuration());
        entity.setCommodityName(commodityInfo.getCommodityName());
        entity.setCommodityNumber(commodityInfo.getCommodityNumber());

        PaymentInfo paymentInfo = context.getPaymentInfo();
        entity.setOrderMoney(paymentInfo.getOrderMoney());
        entity.setFinalAmount(paymentInfo.getFinalAmount());
        entity.setDiscountAmount(paymentInfo.getDiscountAmount());
        entity.setCouponIds(paymentInfo.getCouponIds());
        entity.setUseCoupon(CollectionUtil.isNotEmpty(paymentInfo.getCouponIds())
                ? YesNoFlag.YES.getCode()
                : YesNoFlag.NO.getCode());
        entity.setPayMethod(resolvePayMethod(paymentInfo.getPayMethod()));
        entity.setPaymentSource(context.resolvePaymentSource().getCode());
        entity.setSourceRewardId(context.getSourceRewardId());

        entity.setPurchaserBy(context.getPurchaserBy());
        entity.setPurchaserTime(LocalDateTime.now());
        entity.setWeiChatCode(context.getWeiChatCode());
        entity.setRemark(context.getRemark());
        return entity;
    }

    private void applyRandomOrderRequirements(PlayOrderInfoEntity entity, RandomOrderRequirements requirements) {
        if (requirements == null) {
            return;
        }
        entity.setSex(requirements.getClerkGender().getCode());
        entity.setLevelId(requirements.getClerkLevelId());
        entity.setExcludeHistory(requirements.getExcludeHistory());
    }

    private void applyAcceptByInfo(PlayOrderInfoEntity entity, OrderCreationContext context) {
        if (StrUtil.isBlank(context.getAcceptBy())) {
            return;
        }
        entity.setAcceptBy(context.getAcceptBy());
        ClerkEstimatedRevenueVo estimatedRevenue = clerkRevenueCalculator.calculateEstimatedRevenue(
                context.getAcceptBy(),
                context.getPaymentInfo().getCouponIds(),
                context.getPlaceType().getCode(),
                entity.getFirstOrder(),
                context.getPaymentInfo().getOrderMoney());
        entity.setEstimatedRevenue(estimatedRevenue.getRevenueAmount());
        entity.setEstimatedRevenueRatio(estimatedRevenue.getRevenueRatio());
    }

    private void applyRewardOrderDefaults(PlayOrderInfoEntity entity) {
        LocalDateTime now = LocalDateTime.now();
        entity.setAcceptTime(now);
        entity.setOrderStartTime(now);
        entity.setOrderEndTime(now);
    }

    private void updateCouponUsage(List<String> couponIds) {
        if (CollectionUtil.isEmpty(couponIds)) {
            return;
        }
        playCouponDetailsService.updateCouponUseStateByIds(couponIds, CouponUseState.USED.getCode());
    }

    BigDecimal normalizeMoney(BigDecimal amount) {
        if (amount == null) {
            return BigDecimal.ZERO.setScale(MONEY_SCALE, RoundingMode.HALF_UP);
        }
        return amount.setScale(MONEY_SCALE, RoundingMode.HALF_UP);
    }

    private String resolvePayMethod(String payMethodCode) {
        if (StrUtil.isBlank(payMethodCode)) {
            return PayMethod.BALANCE.getCode();
        }
        try {
            return PayMethod.fromCode(payMethodCode).getCode();
        } catch (IllegalArgumentException ex) {
            log.warn("Unknown pay method code {}, fallback to BALANCE", payMethodCode);
            return PayMethod.BALANCE.getCode();
        }
    }

    private String resolveFirstOrderFlag(OrderCreationContext context) {
        if (StrUtil.isBlank(context.getAcceptBy()) || StrUtil.isBlank(context.getPurchaserBy())) {
            return context.getFirstOrderString();
        }
        return isFirstOrder(context.getPurchaserBy(), context.getAcceptBy())
                ? YesNoFlag.YES.getCode()
                : YesNoFlag.NO.getCode();
    }

    private boolean isFirstOrder(String customerId, String clerkId) {
        LambdaQueryWrapper<PlayOrderInfoEntity> wrapper = Wrappers.lambdaQuery(PlayOrderInfoEntity.class)
                .eq(PlayOrderInfoEntity::getPurchaserBy, customerId)
                .eq(PlayOrderInfoEntity::getAcceptBy, clerkId)
                .eq(PlayOrderInfoEntity::getOrderStatus, OrderStatus.COMPLETED.getCode());
        return orderInfoMapper.selectCount(wrapper) == 0;
    }

    private boolean ensureEarnings(PlayOrderInfoEntity order, OrderTriggerSource source) {
        if (OrderConstant.OrderType.BLIND_BOX_PURCHASE.getCode().equals(order.getOrderType())) {
            log.debug("Skip earnings creation for blind box purchase order {}", order.getId());
            return false;
        }
        Long existing = earningsService.lambdaQuery()
                .eq(EarningsLineEntity::getTenantId, order.getTenantId())
                .eq(EarningsLineEntity::getOrderId, order.getId())
                .count();
        if (existing != null && existing > 0) {
            return false;
        }
        earningsService.createFromOrder(order);
        Long after = earningsService.lambdaQuery()
                .eq(EarningsLineEntity::getTenantId, order.getTenantId())
                .eq(EarningsLineEntity::getOrderId, order.getId())
                .count();
        if (after == null || after == 0) {
            log.warn("Failed to create earnings line for order {}, source={}", order.getId(), source.getCode());
            return false;
        }
        return true;
    }

    private boolean hasLifecycleLog(String orderId, LifecycleOperation operation) {
        if (StrUtil.isBlank(orderId)) {
            return false;
        }
        return orderLogInfoMapper.selectCount(Wrappers.<PlayOrderLogInfoEntity>lambdaQuery()
                .eq(PlayOrderLogInfoEntity::getOrderId, orderId)
                .likeRight(PlayOrderLogInfoEntity::getOperationType, operation.name())) > 0;
    }

    private String resolveCreationOperationType(OrderActor creatorActor) {
        String actorSegment = creatorActor != null ? creatorActor.name() : OrderActor.SYSTEM.name();
        return String.format("%s_%s", LifecycleOperation.CREATE.name(), actorSegment);
    }

    private String resolveCompletionOperationType(OrderCompletionContext context, boolean transitioned) {
        OrderActor operatorActor = context != null ? context.getOperatorActor() : OrderActor.SYSTEM;
        String actorSegment = operatorActor != null ? operatorActor.name() : OrderActor.SYSTEM.name();
        if (transitioned) {
            return String.format("%s_%s", LifecycleOperation.COMPLETE.name(), actorSegment);
        }
        if (context != null && context.isForceNotify()) {
            return String.format("%s_FORCE_%s", LifecycleOperation.COMPLETE.name(), actorSegment);
        }
        return LifecycleOperation.COMPLETE.name();
    }

    public static final class LifecycleToken {
        private LifecycleToken() {}
    }

    private void recordOrderLog(PlayOrderInfoEntity order,
                                OrderActor actor,
                                String operatorId,
                                LifecycleOperation operation,
                                String remark,
                                String operationType) {
        if (order == null || StrUtil.isBlank(order.getId())) {
            return;
        }
        PlayOrderLogInfoEntity log = new PlayOrderLogInfoEntity();
        log.setId(IdUtils.getUuid());
        log.setTenantId(order.getTenantId());
        log.setOrderId(order.getId());
        log.setOperationType(StrUtil.isNotBlank(operationType) ? operationType : operation.name());
        log.setOperatorType(actor != null ? actor.name() : null);
        log.setOperatorId(operatorId);
        log.setOperTime(LocalDateTime.now());
        log.setRemark(remark);
        orderLogInfoMapper.insert(log);
    }

    private OrderActor resolveCompletionActor(String operatorType) {
        if (StrUtil.isBlank(operatorType)) {
            return OrderActor.SYSTEM;
        }
        try {
            OperatorType type = OperatorType.fromCode(operatorType);
            switch (type) {
                case CUSTOMER:
                    return OrderActor.CUSTOMER;
                case CLERK:
                    return OrderActor.CLERK;
                case GROUP_LEADER:
                    return OrderActor.GROUP_LEADER;
                case ADMIN:
                    return OrderActor.ADMIN;
                default:
                    return OrderActor.SYSTEM;
            }
        } catch (IllegalArgumentException ex) {
            log.warn("Unknown operator type {}, fallback to SYSTEM", operatorType, ex);
            return OrderActor.SYSTEM;
        }
    }
}
