package com.haha.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.haha.common.config.CommonConfig;
import com.haha.common.constant.PaymentConstants;
import com.haha.common.constant.RedisConstants;
import com.haha.common.enums.ApplyScopeEnum;
import com.haha.common.enums.CouponTypeEnum;
import com.haha.common.enums.DeviceDoorStatus;
import com.haha.common.enums.DeviceOnlineStatus;
import com.haha.common.enums.NotifyType;
import com.haha.common.enums.OpenType;
import com.haha.common.enums.PayScoreState;
import com.haha.common.enums.PaymentChannel;
import com.haha.common.enums.ProductAuditStatus;
import com.haha.common.enums.RecognizeActionType;
import com.haha.common.enums.RecognizeConsumeType;
import com.haha.entity.Order;
import com.haha.entity.Device;
import com.haha.entity.OrderGoods;
import com.haha.entity.DoorRecord;
import com.haha.entity.UserCoupon;
import com.haha.entity.CouponTemplate;
import com.haha.mapper.DeviceMapper;
import com.haha.mapper.OrderGoodsMapper;
import com.haha.sdk.HahaClient;
import com.haha.service.DeviceAlertService;
import com.haha.service.HahaCallbackService;
import com.haha.service.DataSyncService;
import com.haha.service.NewProductApplyService;
import com.haha.service.OrderService;
import com.haha.service.DoorRecordService;
import com.haha.service.UserCouponService;
import com.haha.service.CouponTemplateService;
import com.haha.service.config.DeviceAlertProperties;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.haha.service.payment.payscore.PayScoreCreateRequest;
import com.haha.service.payment.payscore.PayScoreResult;
import com.haha.service.payment.payscore.PayScoreService;
import org.springframework.data.redis.core.StringRedisTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class HahaCallbackServiceImpl implements HahaCallbackService {

    @Autowired
    private OrderService orderService;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private OrderGoodsMapper orderGoodsMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private HahaClient hahaClient;

    @Autowired
    private NewProductApplyService newProductApplyService;

    @Autowired(required = false)
    private PayScoreService payScoreService;

    @Autowired
    private com.haha.common.config.CommonConfig commonConfig;

    @Autowired
    private DoorRecordService doorRecordService;

    @Autowired
    private UserCouponService userCouponService;

    @Autowired
    private CouponTemplateService couponTemplateService;

    @Autowired
    private DeviceAlertService deviceAlertService;

    @Autowired
    private DeviceAlertProperties alertProperties;

    @Autowired
    private DataSyncService dataSyncService;

    @Autowired
    private com.haha.service.ProductService productService;

    @Autowired
    private com.haha.service.OrderInventoryService orderInventoryService;

    @Override
    public void handleMessage(Map<String, Object> params, String rawBody) {
        // 签名验证：确保回调来自哈哈平台
        if (!validateSign(params, rawBody)) {
            log.warn("消息回调签名验证失败，忽略该通知");
            return;
        }

        String notifyType = extractParam(params, "notify_type");
        if (notifyType == null || notifyType.isEmpty()) {
            log.warn("消息回调缺少 notify_type 参数");
            return;
        }

        NotifyType type = NotifyType.fromCode(notifyType);
        if (type == null) {
            log.warn("未知的消息通知类型: {}", notifyType);
            return;
        }

        switch (type) {
            case DEVICE_STATUS:
                handleDeviceStatus(params);
                break;
            case ONLINE_STATUS:
                handleOnlineStatus(params);
                break;
            case VOICE_RESULT:
                handleVoiceResult(params);
                break;
            case CLIENT_NEW_PRODUCT:
                handleNewProductAudit(params);
                break;
            case MERGE_PRODUCT:
                handleMergeProduct(params);
                break;
            case ORC_RESULT:
                handleOrcResult(params);
                break;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleDeviceStatus(Map<String, Object> params) {
        try {
            String activityId = extractParam(params, "activity_id");
            String deviceId = extractParam(params, "device_id");
            String doorStatus = extractParam(params, "status");
            String openType = extractParam(params, "open_type");
            String outUserId = extractParam(params, "out_user_id");

            log.info("开关门状态通知 - 设备: {}, 状态: {}, 类型: {}, 用户: {}",
                deviceId, doorStatus, openType, outUserId);

            saveDeviceStatusToRedis(deviceId, activityId, doorStatus, openType, outUserId);

            DeviceDoorStatus doorStatusEnum = DeviceDoorStatus.fromCode(doorStatus);
            if (doorStatusEnum != null) {
                switch (doorStatusEnum) {
                    case OPENED:
                        log.info("设备 {} 开门成功", deviceId);
                        break;
                    case CLOSED:
                        log.info("设备 {} 关门成功,等待 AI 识别", deviceId);
                        if (activityId != null && !activityId.isEmpty()) {
                            doorRecordService.updateDoorClosed(activityId);
                        }
                        break;
                    case ERROR:
                        log.error("设备 {} 开门失败", deviceId);
                        break;
                    case BUSY:
                        log.warn("设备 {} 繁忙", deviceId);
                        break;
                }
            }

            updateDeviceDoorStatus(deviceId, doorStatus);

        } catch (Exception e) {
            log.error("处理开关门状态通知失败", e);
        }
    }

    @Override
    public void handleOnlineStatus(Map<String, Object> params) {
        try {
            String deviceId = extractParam(params, "device_id");
            Integer isOnline = parseInteger(params.get("is_online"));

            if (isOnline == null) {
                log.warn("设备在线状态为空 - 设备: {}", deviceId);
                return;
            }

            log.info("设备在线状态通知 - 设备: {}, 在线状态: {}", deviceId, DeviceOnlineStatus.isOnline(isOnline) ? "在线" : "离线");

            String onlineKey = "device:online:" + deviceId;
            stringRedisTemplate.opsForValue().set(onlineKey, isOnline.toString(), 10, TimeUnit.MINUTES);

            // === 新增：设备离线预警处理 ===
            // 解析 signal_val（信号值数组）
            List<Map<String, Object>> signalVals = null;
            Object signalValObj = params.get("signal_val");
            if (signalValObj instanceof List) {
                signalVals = (List<Map<String, Object>>) signalValObj;
            }
            Integer latestSignalVal = extractLatestValue(signalVals);

            if (isOnline == DeviceOnlineStatus.OFFLINE.getCode()) {
                // 设备离线 -> 触发离线告警
                deviceAlertService.processOfflineAlert(deviceId, "CALLBACK");
            } else if (isOnline == DeviceOnlineStatus.ONLINE.getCode()) {
                // 设备上线 -> 检查是否为新设备，自动同步
                Device localDevice = deviceMapper.selectByDeviceId(deviceId);
                if (localDevice == null) {
                    log.info("设备 {} 在本地不存在，触发异步同步", deviceId);
                    dataSyncService.syncDeviceBySn(deviceId);
                }

                // 设备上线 -> 检查是否需要发送恢复通知
                deviceAlertService.processBackOnlineNotify(deviceId);

                // 检查信号强度
                if (latestSignalVal != null && latestSignalVal <= alertProperties.getWeakSignalThreshold()) {
                    deviceAlertService.processWeakSignalAlert(deviceId, latestSignalVal, "CALLBACK");
                }
            }

        } catch (Exception e) {
            log.error("处理设备在线状态通知失败", e);
        }
    }

    @Override
    public void handleVoiceResult(Map<String, Object> params) {
        try {
            String deviceId = extractParam(params, "device_id");
            Integer voice = parseInteger(params.get("voice"));

            if (voice == null) {
                log.warn("音量值为空 - 设备: {}", deviceId);
                return;
            }

            log.info("音量调节结果通知 - 设备: {}, 音量值: {}", deviceId, voice);

            String voiceKey = "device:voice:" + deviceId;
            stringRedisTemplate.opsForValue().set(voiceKey, voice.toString(), 30, TimeUnit.DAYS);

        } catch (Exception e) {
            log.error("处理音量调节结果通知失败", e);
        }
    }

    @Override
    public void handleNewProductAudit(Map<String, Object> params) {
        try {
            String id = extractParam(params, "id");
            Integer status = parseInteger(params.get("status"));
            String name = extractParam(params, "name");
            String code = extractParam(params, "code");
            String barCode = extractParam(params, "bar_code");
            String rejectReason = extractParam(params, "reject_reason");

            log.info("新品审核结果 - ID: {}, 商品名: {}, 状态: {}, code: {}, barCode: {}",
                    id, name, status, code, barCode);

            if (status != null && status == ProductAuditStatus.REJECTED.getCode()) {
                log.warn("新品 {} 被拒绝,原因: {}", name, rejectReason);
            }

            newProductApplyService.updateByCallback(id, status, code, rejectReason);

            if (status != null && status == ProductAuditStatus.LISTED.getCode() && code != null && !code.isEmpty()) {
                syncProductToMerchant(code, name);
            }

            String auditKey = "product:audit:" + id;
            stringRedisTemplate.opsForValue().set(auditKey, JSON.toJSONString(params), 7, TimeUnit.DAYS);

        } catch (Exception e) {
            log.error("处理新品审核结果通知失败", e);
        }
    }

    private void syncProductToMerchant(String code, String name) {
        try {
            log.info("开始同步商品到商家商品库 - code: {}, name: {}", code, name);

            boolean success = hahaClient.getGoodsApi().addToMerchant(code);

            if (success) {
                log.info("商品同步成功 - code: {}, name: {}", code, name);
            } else {
                log.warn("商品同步失败 - code: {}, name: {}", code, name);
            }
        } catch (Exception e) {
            log.error("同步商品到商家商品库异常 - code: {}, name: {}", code, name, e);
        }
    }

    @Override
    public void handleMergeProduct(Map<String, Object> params) {
        try {
            Object listObj = params.get("list");

            if (listObj instanceof JSONArray list) {
                log.info("商品合并结果通知 - 合并数量: {}", list.size());

                for (Object item : list) {
                    if (item instanceof JSONObject merge) {
                        String mainCode = merge.getString("main_code");
                        String productCode = merge.getString("product_code");
                        log.info("商品合并 - 主商品: {}, 合并商品: {}", mainCode, productCode);
                    }
                }
            }

        } catch (Exception e) {
            log.error("处理商品合并结果通知失败", e);
        }
    }

    /**
     * 处理AI识别结果通知
     *
     * @param params 消息参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleOrcResult(Map<String, Object> params) {
        String activityId = extractParam(params, "activity_id");
        String deviceId = extractParam(params, "device_id");
        String userId = extractParam(params, "out_user_id");
        Integer nobuy = parseInteger(params.get("nobuy"));
        String resultStr = extractParam(params, "result");
        String skuListStr = extractParam(params, "sku_list");
        String resourceInfoStr = extractParam(params, "resource_info");

        // 参数验证
        if (!validateRequiredParams(activityId, deviceId, userId)) {
            log.warn("AI识别结果通知缺少必要参数 - activityId: {}, deviceId: {}, userId: {}",
                    activityId, deviceId, userId);
            return;
        }

        log.info("AI识别结果通知 - 设备: {}, 活动: {}, 是否消费: {}",
                deviceId, activityId, RecognizeConsumeType.isNoConsume(nobuy) ? "无消费" : "有消费");

        try {
            // 保存识别结果到Redis
            saveRecognizeResultToRedis(activityId, params);

            // 处理无消费场景
            if (RecognizeConsumeType.isNoConsume(nobuy)) {
                handleNoConsume(activityId);
                return;
            }

            // 处理有消费场景
            BigDecimal confidence = parseBigDecimal(params.get("confidence"));
            // 从识别结果中提取类型：OUT-拿出(正常消费)，IN-放入(异物放入)
            String recognizeType = extractRecognizeType(resultStr);
            // 以 result.data 为准构建商品清单（result.data 是总的识别结果，sku_list 仅含自定义商品）
            String effectiveSkuListStr = buildSkuListFromResultData(resultStr, skuListStr);
            handleConsume(activityId, deviceId, userId, effectiveSkuListStr, resourceInfoStr, resultStr, confidence, recognizeType);

        } catch (Exception e) {
            log.error("处理AI识别结果通知失败 - activityId: {}, deviceId: {}", activityId, deviceId, e);
            throw e;
        }
    }

    /**
     * 提取字符串参数
     */
    private String extractParam(Map<String, Object> params, String key) {
        Object value = params.get(key);
        return value != null ? value.toString() : null;
    }

    /**
     * 验证必要参数
     */
    private boolean validateRequiredParams(String activityId, String deviceId, String userId) {
        return activityId != null && !activityId.isEmpty()
                && deviceId != null && !deviceId.isEmpty()
                && userId != null && !userId.isEmpty();
    }

    /**
     * 处理无消费场景
     */
    private void handleNoConsume(String activityId) {
        log.info("用户打开柜门但未消费,无需创建订单 - activityId: {}", activityId);
        doorRecordService.markNoConsume(activityId);
        doorRecordService.updateDoorClosed(activityId);
    }

    /**
     * 处理有消费场景
     */
    private void handleConsume(String activityId, String deviceId, String userId,
                                String skuListStr, String resourceInfoStr, String resultStr,
                                BigDecimal confidence, String recognizeType) {

        // 获取或创建订单
        Order order = getOrCreateOrder(activityId, deviceId, userId, skuListStr, resourceInfoStr, confidence);
        if (order == null) {
            log.warn("无法获取或创建订单 - activityId: {}", activityId);
            return;
        }

        // 设置订单类型（OUT-拿出/IN-放入）- 选择性更新，不覆盖 payScoreOrderId
        if (recognizeType != null && !recognizeType.isEmpty()) {
            order.setOrderType(recognizeType);
            orderService.lambdaUpdate().set(Order::getOrderType, recognizeType).eq(Order::getId, order.getId()).update();
            log.info("订单类型设置成功 - orderId: {}, orderType: {}", order.getId(), recognizeType);

            // IN类型（异物放入）触发告警，提醒管理员审核
            if (RecognizeActionType.IN.getCode().equals(recognizeType)) {
                String alertContent = String.format("AI识别到异物放入(IN类型)，订单号:%s", order.getOrderNo());
                log.warn("[异常订单] {} - orderId: {}", alertContent, order.getId());
                try {
                    deviceAlertService.processAbnormalOrderAlert(deviceId, order.getId(), alertContent);
                } catch (Exception e) {
                    log.error("[异常订单] 触发告警失败 - orderId: {}", order.getId(), e);
                }
            }
        }

        // 更新订单信息
        updateOrderFromRecognition(order, skuListStr, resourceInfoStr, confidence, activityId);
        logRecognizeResultDetails(resultStr, skuListStr, resourceInfoStr);

        // OUT类型（正常取出）：识别后立即扣减库存，因为商品已物理离开柜子
        // IN类型（异物放入）：不扣库存，标记异常等人工审核
        if (!RecognizeActionType.IN.getCode().equals(order.getOrderType())) {
            try {
                orderInventoryService.decreaseStockOnPaid(order);
                log.info("识别后扣减库存: orderNo={}, orderType={}", order.getOrderNo(), order.getOrderType());
            } catch (Exception e) {
                log.error("识别后扣减库存异常: orderNo={}", order.getOrderNo(), e);
            }
        }

        // 低置信度订单拉取开门前后图片供人工审核
        if (confidence != null && confidence.compareTo(PaymentConstants.CONFIDENCE_THRESHOLD) < 0) {
            log.info("识别置信度较低({})，拉取订单图像供人工审核 - activityId: {}", confidence, activityId);
            getOrderMedia(null, activityId);
        }

        // 更新关门状态
        doorRecordService.updateDoorClosed(activityId);
        log.info("AI识别结果处理完成 - activityId: {}, orderId: {}", activityId, order.getId());
    }

    /**
     * 获取或创建订单
     */
    private Order getOrCreateOrder(String activityId, String deviceId, String userId,
                                    String skuListStr, String resourceInfoStr, BigDecimal confidence) {
        // 先尝试从数据库获取已有订单
        Order existingOrder = orderService.getOrderByActivityId(activityId);
        if (existingOrder != null) {
            log.info("找到已有订单 - activityId: {}, orderId: {}", activityId, existingOrder.getId());
            // 订单已存在但可能缺少支付分信息（如 fetchRecognitionAndCreateOrder 创建的），尝试补齐
            tryApplyPreAuthPayScore(existingOrder);
            return existingOrder;
        }

        // 尝试从开门记录创建订单
        DoorRecord record = doorRecordService.getByActivityId(activityId);
        if (record == null) {
            log.warn("未找到开门记录 - activityId: {}", activityId);
            return null;
        }

        log.info("根据开门记录创建订单 - activityId: {}, deviceId: {}, userId: {}",
                activityId, record.getDeviceId(), record.getUserId());

        // 提前从 Redis 读取支付分预授权信息，以便 INSERT 时直接写入
        String payScoreOrderId = null;
        String payScoreState = null;
        String serviceId = null;
        if (payScoreService != null) {
            com.alibaba.fastjson2.JSONObject preAuth = ((com.haha.service.payment.payscore.impl.PayScoreServiceImpl) payScoreService)
                    .readPreAuthFromRedis(record.getDeviceId(), record.getUserId());
            if (preAuth != null) {
                // 第一层：Redis 命中
                payScoreOrderId = preAuth.getString("payScoreOrderId");
                payScoreState = preAuth.getString("payScoreState");
                serviceId = preAuth.getString("serviceId");
            } else if (record.getPayScoreOrderId() != null) {
                // 第二层：Redis 过期，从 DoorRecord（DB 持久化）恢复
                payScoreOrderId = record.getPayScoreOrderId();
                payScoreState = PayScoreState.CREATED.getCode();
                serviceId = record.getServiceId();
                log.info("从DoorRecord恢复支付分订单ID - userId: {}, deviceId: {}, payScoreOrderId: {}",
                        record.getUserId(), record.getDeviceId(), payScoreOrderId);
            } else {
                // 第三层：兜底从追踪SET恢复
                payScoreOrderId = ((com.haha.service.payment.payscore.impl.PayScoreServiceImpl) payScoreService)
                        .tryRecoverPayScoreOrderId(record.getUserId(), record.getDeviceId());
                if (payScoreOrderId != null) {
                    payScoreState = PayScoreState.CREATED.getCode();
                    serviceId = payScoreService.getServiceId();
                    log.info("从追踪SET恢复支付分订单ID成功 - userId: {}, deviceId: {}, payScoreOrderId: {}",
                            record.getUserId(), record.getDeviceId(), payScoreOrderId);
                } else {
                    log.warn("Redis/DoorRecord/追踪SET 均未找到支付分信息 - userId: {}, deviceId: {}",
                            record.getUserId(), record.getDeviceId());
                }
            }
        }

        Order newOrder = orderService.createOrderFromRecognition(
                activityId,
                record.getDeviceId(),
                String.valueOf(record.getUserId()),
                skuListStr,
                resourceInfoStr,
                confidence,
                payScoreOrderId,
                payScoreState,
                serviceId
        );

        // 关联开门记录和订单
        if (newOrder != null) {
            doorRecordService.linkOrderId(activityId, newOrder.getId());
            log.info("订单创建成功并已关联开门记录 - orderId: {}", newOrder.getId());

            // 清理 Redis 中的预授权信息（如果订单已有支付分信息，仅删 Redis；否则尝试补齐）
            tryApplyPreAuthPayScore(newOrder);
        } else {
            log.error("订单创建失败 - activityId: {}", activityId);
        }

        return newOrder;
    }

    /**
     * 尝试关联支付分预授权信息到订单（幂等，仅当订单缺少支付分信息时执行）
     */
    private void tryApplyPreAuthPayScore(Order order) {
        if (order == null) {
            return;
        }
        if (payScoreService == null) {
            // 仅当订单缺少支付分信息时才打 WARN，已有则无需告警
            if (order.getPayScoreOrderId() == null) {
                log.warn("[支付分配置] payScoreService 未注入，跳过关联 - orderId: {}", order.getId());
            }
            return;
        }
        try {
            ((com.haha.service.payment.payscore.impl.PayScoreServiceImpl) payScoreService)
                    .applyPreAuthPayScore(order);
        } catch (Exception e) {
            log.warn("关联支付分预授权信息失败 - orderId: {}, error: {}", order.getId(), e.getMessage());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleOrderCallback(Map<String, Object> params, String rawBody) {
        // 签名验证：确保订单回调来自哈哈平台
        if (!validateSign(params, rawBody)) {
            log.warn("订单回调签名验证失败，忽略该通知");
            return;
        }

        try {
            String orderId = extractParam(params, "order_id");
            String deviceId = extractParam(params, "device_id");
            String activityId = extractParam(params, "activity_id");
            String userId = extractParam(params, "user_id");
            Object orderMoney = params.get("order_money");
            String orderDetail = extractParam(params, "order_detail");
            String orderName = extractParam(params, "order_name");
            String orderGoodsStr = extractParam(params, "order_goods");

            log.info("订单信息 - 订单号: {}, 设备: {}, 活动ID: {}, 用户: {}, 金额: {}, 商品: {}",
                orderId, deviceId, activityId, userId, orderMoney, orderName);

            Order localOrder = null;
            if (activityId != null && !activityId.isEmpty()) {
                localOrder = orderService.getOrderByActivityId(activityId);
            }

            if (localOrder == null && userId != null && deviceId != null) {
                log.info("订单不存在,主动查询识别结果 - activityId: {}", activityId);
                localOrder = fetchRecognitionAndCreateOrder(activityId, deviceId, userId);
            }

            if (localOrder == null) {
                log.warn("无法创建订单 - 缺少必要信息: userId={}, deviceId={}, activityId={}", userId, deviceId, activityId);
                return;
            }

            // 如果订单已有金额且与回调金额一致，说明是重复回调，跳过
            if (orderMoney != null && localOrder.getTotalAmount() != null
                    && localOrder.getTotalAmount().compareTo(BigDecimal.ZERO) > 0
                    && localOrder.getTotalAmount().compareTo(new BigDecimal(orderMoney.toString())) == 0) {
                log.info("订单已处理过(金额一致),跳过更新 - orderId: {}", orderId);
                saveOrderInfoToRedis(localOrder, activityId, orderId, orderMoney, orderDetail);
                return;
            }

            updateOrderFromCallback(localOrder, orderId, activityId, orderMoney, orderDetail, orderName, orderGoodsStr);
            saveOrderInfoToRedis(localOrder, activityId, orderId, orderMoney, orderDetail);

            // 处理优惠券扣减（在订单金额更新后）
            BigDecimal finalAmount = processCouponDiscount(localOrder, orderMoney);

            // 处理支付分扣费(如果订单使用了支付分)
            // 注意：如果有优惠券，使用优惠后的金额
            processPayScorePayment(localOrder, finalAmount);


        } catch (Exception e) {
            log.error("处理订单回调通知失败", e);
        }
    }

    @Override
    public boolean validateSign(Map<String, Object> params) {
        return validateSign(params, null);
    }

    @Override
    public boolean validateSign(Map<String, Object> params, String rawBody) {
        String receivedSign = extractParam(params, "sign");
        if (receivedSign == null || receivedSign.isEmpty()) {
            log.warn("签名参数为空，回调来源可能未经验证");
            return false;
        }

        // 获取 ticket（从 /token/get 接口返回）
        String ticket;
        try {
            hahaClient.getAccessToken();
            ticket = hahaClient.getTicket();
        } catch (Exception e) {
            log.error("获取验签 ticket 失败", e);
            return false;
        }

        if (ticket == null || ticket.isEmpty()) {
            log.error("ticket 为空，无法验证签名");
            return false;
        }

        // 时间戳防重放攻击（5分钟有效期）
        if (params.containsKey("timestamp")) {
            try {
                long ts = Long.parseLong(params.get("timestamp").toString());
                long now = System.currentTimeMillis() / 1000;
                if (Math.abs(now - ts) > 300) {
                    log.warn("回调时间戳超出有效期 - timestamp: {}, now: {}", ts, now);
                    return false;
                }
            } catch (NumberFormatException e) {
                log.warn("回调时间戳格式错误: {}", params.get("timestamp"));
            }
        }

        // 哈哈平台回调签名规则（官方文档）：
        // 第一步：参数按键排序，值 URL 编码后拼接为 k1=v1&k2=v2&...
        // 第二步：末尾直接拼接 ticket（无 & 或 = 分隔符）
        // 第三步：MD5 得到签名
        String signContent;
        if (rawBody != null && !rawBody.isEmpty()) {
            // 优先：从原始请求体构建签名，保留 PHP 端的原始 URL 编码
            signContent = buildSignContentFromRawBody(rawBody);
        } else {
            // fallback：从已解析的参数 Map 构建（保留兼容）
            signContent = buildSignContent(params);
        }
        String signStr = signContent + ticket;
        String computedSign = md5(signStr);

        boolean isValid = computedSign.equalsIgnoreCase(receivedSign);
        if (!isValid) {
            log.warn("签名验证失败 - 计算签名: {}, 接收签名: {}, 签名原文: {}",
                    computedSign, receivedSign, signStr);
            // 如果 rawBody 方式失败，尝试用旧方式再验一次（可能是 JSON 格式的回调）
            if (rawBody != null && !rawBody.isEmpty()) {
                String fallbackSignContent = buildSignContent(params);
                String fallbackSignStr = fallbackSignContent + ticket;
                String fallbackSign = md5(fallbackSignStr);
                if (fallbackSign.equalsIgnoreCase(receivedSign)) {
                    log.info("签名验证通过（fallback 方式）");
                    return true;
                }
                log.warn("两种签名方式均验证失败 - rawBody方式: {}, fallback方式: {}",
                        computedSign, fallbackSign);
            }
        } else {
            log.debug("签名验证通过（rawBody方式）");
        }
        return isValid;
    }

    /**
     * 从原始 application/x-www-form-urlencoded 请求体构建待签名字符串
     * 保留 PHP 端的原始 URL 编码，避免 Spring decode → Java URLEncoder re-encode 的编码差异
     *
     * 原始 body 格式: k1=v1&k2=v2&...（值已 URL 编码）
     * 处理方法：按键名排序后重新拼接，保持值的原始编码不变
     */
    private String buildSignContentFromRawBody(String rawBody) {
        if (rawBody == null || rawBody.isEmpty()) {
            return "";
        }

        // 解析原始 body 为 key=value 对，保持值的原始编码
        List<NameValuePair> pairs = new ArrayList<>();
        String[] parts = rawBody.split("&");
        for (String part : parts) {
            int eqIdx = part.indexOf('=');
            if (eqIdx > 0) {
                String key = part.substring(0, eqIdx);
                String value = part.substring(eqIdx + 1);
                // 排除 sign 和 sign_type 参数
                if (!"sign".equals(key) && !"sign_type".equals(key)) {
                    pairs.add(new NameValuePair(key, value));
                }
            }
        }

        // 按 key 字典序排序
        pairs.sort(java.util.Comparator.comparing(p -> p.key));

        // 拼接签名原文: k1=v1&k2=v2&...
        return pairs.stream()
                .map(p -> p.key + "=" + p.value)
                .collect(Collectors.joining("&"));
    }

    /**
     * 简单的 key-value 对（内部使用）
     */
    private static class NameValuePair {
        final String key;
        final String value;

        NameValuePair(String key, String value) {
            this.key = key;
            this.value = value;
        }
    }

    /**
     * 构建待签名字符串
     * 将所有参数（排除 sign, sign_type）按 key 字典序排序，
     * 值进行 URL 编码后拼接为 key=value 格式，以 & 连接
     */
    private String buildSignContent(Map<String, Object> params) {
        return params.entrySet().stream()
                .filter(e -> !"sign".equals(e.getKey()) && !"sign_type".equals(e.getKey()))
                .filter(e -> e.getValue() != null)
                .sorted(Map.Entry.comparingByKey())
                .map(e -> e.getKey() + "=" + urlEncode(e.getValue().toString()))
                .collect(Collectors.joining("&"));
    }

    private String urlEncode(String value) {
        try {
            return URLEncoder.encode(value, StandardCharsets.UTF_8);
        } catch (Exception e) {
            return value;
        }
    }

    /**
     * SHA-256 加密（64位小写十六进制）
     * 可用于后续升级验签算法，当前哈哈平台固定使用 MD5
     */
    private String sha256(String input) {
        try {
            java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-256");
            byte[] bytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : bytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (java.security.NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-256 算法不可用", e);
        }
    }

    /**
     * MD5 加密（32位小写十六进制）
     * ⚠️ 哈哈平台回调签名规则固定使用 MD5，不可单方面升级
     * 补偿措施：签名验证前增加时间戳有效性校验
     */
    private String md5(String str) {
        try {
            java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
            byte[] bytes = md.digest(str.getBytes(java.nio.charset.StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : bytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (java.security.NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 算法不可用", e);
        }
    }

    /**
     * 通用 Redis Hash 保存（putAll + expire）
     */
    private void saveHashToRedis(String key, Map<String, String> data, long timeout, TimeUnit unit) {
        try {
            stringRedisTemplate.opsForHash().putAll(key, data);
            stringRedisTemplate.expire(key, timeout, unit);
        } catch (Exception e) {
            log.error("保存数据到Redis失败 - key: {}", key, e);
        }
    }

    private static String nullToEmpty(Object val) {
        return val != null ? val.toString() : "";
    }

    private void saveDeviceStatusToRedis(String deviceId, String activityId, String doorStatus, String openType, String outUserId) {
        String key = RedisConstants.HAHA_DEVICE_STATUS_KEY + deviceId;
        Map<String, String> data = new HashMap<>();
        data.put("deviceId", nullToEmpty(deviceId));
        data.put("activityId", nullToEmpty(activityId));
        data.put("status", nullToEmpty(doorStatus));
        data.put("openType", nullToEmpty(openType));
        data.put("userId", nullToEmpty(outUserId));
        data.put("doorStatus", nullToEmpty(DeviceDoorStatus.convertToStatus(doorStatus)));
        data.put("timestamp", String.valueOf(System.currentTimeMillis()));
        saveHashToRedis(key, data, PaymentConstants.REDIS_DEFAULT_TIMEOUT_MINUTES, TimeUnit.MINUTES);
    }

    private void saveRecognizeResultToRedis(String activityId, Map<String, Object> params) {
        String key = RedisConstants.HAHA_RECOGNIZE_RESULT_KEY + activityId;
        Map<String, String> data = new HashMap<>();
        data.put("activityId", nullToEmpty(activityId));
        data.put("deviceId", nullToEmpty(params.get("device_id")));
        data.put("userId", nullToEmpty(params.get("out_user_id")));
        data.put("nobuy", params.get("nobuy") != null ? String.valueOf(params.get("nobuy")) : "0");
        data.put("result", nullToEmpty(params.get("result")));
        data.put("skuList", nullToEmpty(params.get("sku_list")));
        data.put("resourceInfo", nullToEmpty(params.get("resource_info")));
        data.put("timestamp", String.valueOf(System.currentTimeMillis()));
        saveHashToRedis(key, data, PaymentConstants.REDIS_DEFAULT_TIMEOUT_MINUTES, TimeUnit.MINUTES);
    }

    private void saveOrderInfoToRedis(Order order, String activityId) {
        String key = RedisConstants.HAHA_ORDER_INFO_KEY + activityId;
        stringRedisTemplate.delete(key);
        Map<String, String> data = new HashMap<>();
        data.put("orderId", nullToEmpty(order.getId()));
        data.put("orderNo", nullToEmpty(order.getOrderNo()));
        data.put("activityId", nullToEmpty(activityId));
        data.put("deviceId", nullToEmpty(order.getDeviceId()));
        data.put("userId", nullToEmpty(order.getUserId()));
        data.put("totalAmount", order.getTotalAmount() != null ? order.getTotalAmount().toString() : "0");
        data.put("items", nullToEmpty(order.getItems()));
        data.put("status", nullToEmpty(order.getStatus()));
        data.put("payStatus", nullToEmpty(order.getPayStatus()));
        data.put("timestamp", String.valueOf(System.currentTimeMillis()));
        saveHashToRedis(key, data, PaymentConstants.REDIS_DEFAULT_TIMEOUT_MINUTES, TimeUnit.MINUTES);
    }

    private void saveOrderInfoToRedis(Order order, String activityId, String orderId, Object orderMoney, String orderDetail) {
        String key = RedisConstants.HAHA_ORDER_INFO_KEY + activityId;
        stringRedisTemplate.delete(key);
        Map<String, String> data = new HashMap<>();
        data.put("orderId", nullToEmpty(orderId));
        data.put("activityId", nullToEmpty(activityId));
        data.put("deviceId", nullToEmpty(order.getDeviceId()));
        data.put("userId", nullToEmpty(order.getUserId()));
        data.put("totalAmount", orderMoney != null ? orderMoney.toString() : "0");
        data.put("products", nullToEmpty(orderDetail));
        data.put("timestamp", String.valueOf(System.currentTimeMillis()));
        saveHashToRedis(key, data, PaymentConstants.REDIS_DEFAULT_TIMEOUT_MINUTES, TimeUnit.MINUTES);
    }

    private void updateDeviceDoorStatus(String deviceId, String doorStatus) {
        try {
            String dbDoorStatus = DeviceDoorStatus.convertToDbValue(doorStatus);
            int result = deviceMapper.updateDoorStatus(deviceId, dbDoorStatus);

            if (result > 0) {
                log.info("数据库门状态更新成功 - 设备: {}, 状态: {}", deviceId, dbDoorStatus);
            } else {
                log.warn("数据库门状态更新失败 - 设备: {}, 状态: {}", deviceId, dbDoorStatus);
            }
        } catch (Exception e) {
            log.error("更新数据库门状态异常 - 设备: {}", deviceId, e);
        }
    }

    /**
     * 根据AI识别结果更新订单信息
     *
     * 注意：
     * - 此时只更新商品列表和视频URL，不更新金额
     * - 金额由设备端在订单回调（handleOrderCallback）时提供
     * - AI识别结果只包含商品编码和数量，不包含价格
     *
     * @param order          订单对象
     * @param skuListStr     商品列表字符串（包含code和quantity）
     * @param resourceInfoStr 资源信息字符串
     * @param confidence     置信度
     * @param activityId     活动ID
     */
    private void updateOrderFromRecognition(Order order, String skuListStr, String resourceInfoStr, BigDecimal confidence, String activityId) {
        // 解析视频URL
        String videoUrl = parseVideoUrl(resourceInfoStr);

        // 选择性更新，只更新识别结果相关字段，不覆盖 payScoreOrderId/payChannel
        LambdaUpdateWrapper<Order> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(Order::getId, order.getId());
        updateWrapper.set(Order::getItems, skuListStr);
        order.setItems(skuListStr);

        if (videoUrl != null) {
            updateWrapper.set(Order::getVideoUrl, videoUrl);
            order.setVideoUrl(videoUrl);
        }
        if (confidence != null) {
            updateWrapper.set(Order::getConfidence, confidence);
            order.setConfidence(confidence);
        }

        boolean updated = orderService.update(updateWrapper);
        if (updated) {
            log.info("订单识别信息更新成功 - orderId: {}, 等待回调更新金额", order.getId());
        }

        // 解析sku_list并保存到order_goods表，确保运营平台能立即看到商品信息
        saveOrderGoodsFromSkuList(order, skuListStr);

        saveOrderInfoToRedis(order, activityId);
    }

    /**
     * 从 result.data 构建有效的 sku_list 格式商品列表。
     *
     * 背景：哈哈平台 result.data 是总的商品识别结果（key=商品编码, value=数量变化），
     * sku_list 仅包含自定义商品的附加信息（如 sku 字段）。
     * 以 result.data 为准构建商品清单，同时合并原始 sku_list 中的附加字段。
     *
     * @param resultStr  识别结果 JSON，格式: {"type":"OUT","data":{"code1":-1,...},...}
     * @param skuListStr 原始 sku_list JSON 数组
     * @return 合并后的 sku_list JSON 数组字符串；若 resultStr 为空则回退到原始 skuListStr
     */
    private String buildSkuListFromResultData(String resultStr, String skuListStr) {
        if (resultStr == null || resultStr.isEmpty()) {
            return skuListStr;
        }
        try {
            JSONObject result = JSON.parseObject(resultStr);
            if (result == null) {
                return skuListStr;
            }
            JSONObject data = result.getJSONObject("data");
            if (data == null || data.isEmpty()) {
                log.debug("result.data 为空，回退到原始 sku_list");
                return skuListStr;
            }

            // 从原始 sku_list 提取自定义商品的附加字段（如 sku 字段）
            Map<String, String> customSkuMap = new HashMap<>();
            if (skuListStr != null && !skuListStr.isEmpty()) {
                JSONArray originalSkuList = JSON.parseArray(skuListStr);
                if (originalSkuList != null) {
                    for (int i = 0; i < originalSkuList.size(); i++) {
                        JSONObject item = originalSkuList.getJSONObject(i);
                        String code = item.getString("code");
                        String sku = item.getString("sku");
                        if (code != null && sku != null && !sku.isEmpty()) {
                            customSkuMap.put(code, sku);
                        }
                    }
                }
            }

            // 从 result.data 构建 sku_list，以 result.data 为准
            JSONArray mergedSkuList = new JSONArray();
            int index = 1;
            for (String code : data.keySet()) {
                Integer number = data.getInteger(code);
                JSONObject item = new JSONObject();
                item.put("code", code);
                item.put("number", number);
                item.put("index", index++);
                // 合并自定义商品的 sku 字段
                item.put("sku", customSkuMap.getOrDefault(code, ""));
                mergedSkuList.add(item);
            }

            log.info("从 result.data 构建 sku_list - 商品数: {}, 原始 sku_list 条目数: {}",
                    mergedSkuList.size(), skuListStr != null ? JSON.parseArray(skuListStr).size() : 0);
            return mergedSkuList.toJSONString();
        } catch (Exception e) {
            log.error("从 result.data 构建 sku_list 失败，回退到原始 sku_list", e);
            return skuListStr;
        }
    }

    /**
     * 从 AI 识别的 sku_list 解析商品编码，查本地商品库补全信息后写入 order_goods
     * 确保 ORDER 回调到达前运营平台也能看到订单商品
     */
    private void saveOrderGoodsFromSkuList(Order order, String skuListStr) {
        if (skuListStr == null || skuListStr.isEmpty()) {
            return;
        }
        try {
            // 检查是否已有商品记录（ORDER 回调可能已经写入），避免重复
            Long existingCount = orderGoodsMapper.selectCount(
                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<OrderGoods>()
                            .eq(OrderGoods::getOrderId, order.getId()));
            if (existingCount != null && existingCount > 0) {
                log.debug("订单商品已存在，跳过 sku_list 写入 - orderId: {}", order.getId());
                return;
            }

            JSONArray skuList = JSON.parseArray(skuListStr);
            if (skuList == null || skuList.isEmpty()) {
                return;
            }

            for (int i = 0; i < skuList.size(); i++) {
                JSONObject sku = skuList.getJSONObject(i);
                String code = sku.getString("code");
                Integer number = sku.getInteger("number");
                if (code == null || code.isEmpty()) {
                    continue;
                }

                // 查询本地商品库
                com.haha.entity.Product product = productService.getProductByCode(code);

                OrderGoods goods = new OrderGoods();
                goods.setOrderId(order.getId());
                goods.setOrderNo(order.getOrderNo());
                goods.setActivityId(order.getActivityId());
                goods.setDeviceId(order.getDeviceId());
                goods.setUserId(order.getUserId());
                goods.setCode(code);
                goods.setProductNum(number != null ? Math.abs(number) : 1);
                goods.setCreateTime(LocalDateTime.now());

                if (product != null) {
                    goods.setProductId(product.getId());
                    goods.setBarCode(product.getBarcode());
                    goods.setProductName(product.getName());
                    goods.setPic(product.getPic());
                    if (product.getRetailPrice() != null) {
                        goods.setPrice(BigDecimal.valueOf(product.getRetailPrice()));
                        goods.setMoney(BigDecimal.valueOf(product.getRetailPrice()));
                    }
                }

                orderGoodsMapper.insert(goods);
            }

            log.info("从sku_list保存订单商品成功 - orderId: {}, 商品数: {}", order.getId(), skuList.size());
        } catch (Exception e) {
            log.error("从sku_list保存订单商品失败 - orderId: {}", order.getId(), e);
        }
    }

    private void updateOrderFromCallback(Order order, String orderId, String activityId,
                                          Object orderMoney, String orderDetail,
                                          String orderName, String orderGoodsStr) {
        // 使用选择性更新，只更新回调提供的字段，避免覆盖 payScoreOrderId/payChannel 等
        LambdaUpdateWrapper<Order> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(Order::getId, order.getId());

        updateWrapper.set(Order::getHahaOrderId, orderId);
        updateWrapper.set(Order::getActivityId, activityId);

        if (orderName != null && !orderName.isEmpty()) {
            updateWrapper.set(Order::getOrderName, orderName);
            // 根据订单名称判断订单类型：包含"放入"的为IN类型（异物放入），其他为OUT类型（正常消费）
            if (orderName.contains("放入")) {
                updateWrapper.set(Order::getOrderType, OpenType.IN.getCode());
                order.setOrderType(OpenType.IN.getCode());
            }
        }

        // 如果订单类型仍为空，默认为OUT（正常消费）
        if (order.getOrderType() == null || order.getOrderType().isEmpty()) {
            updateWrapper.set(Order::getOrderType, OpenType.OUT.getCode());
            order.setOrderType(OpenType.OUT.getCode());
        }

        // IN类型订单触发告警（基于订单名称判断）
        if (OpenType.IN.getCode().equals(order.getOrderType())) {
            String alertContent = String.format("订单回调识别为IN类型(异物放入)，订单名称:%s", orderName);
            log.warn("[异常订单] {} - orderId: {}, orderNo: {}", alertContent, order.getId(), orderId);
            try {
                deviceAlertService.processAbnormalOrderAlert(order.getDeviceId(), order.getId(), alertContent);
            } catch (Exception e) {
                log.error("[异常订单] 触发告警失败 - orderId: {}", order.getId(), e);
            }
        }

        if (orderDetail != null) {
            updateWrapper.set(Order::getItems, orderDetail);
            order.setItems(orderDetail);
        }

        if (orderMoney != null) {
            // 设备端传来的金额是已经计算过营销活动优惠后的金额
            BigDecimal totalAmount = new BigDecimal(orderMoney.toString());
            updateWrapper.set(Order::getTotalAmount, totalAmount);
            // 初始化优惠金额和实付金额（后续会被processCouponDiscount更新）
            updateWrapper.set(Order::getDiscountAmount, BigDecimal.ZERO);
            updateWrapper.set(Order::getPaidAmount, totalAmount);
            order.setTotalAmount(totalAmount);
            order.setDiscountAmount(BigDecimal.ZERO);
            order.setPaidAmount(totalAmount);
        }

        boolean updated = orderService.update(updateWrapper);
        if (updated) {
            log.info("订单信息更新成功: {}", orderId);

            saveOrderGoods(order.getId(), orderId, activityId, orderGoodsStr, order.getDeviceId(), order.getUserId());
        } else {
            log.error("订单信息更新失败: {}", orderId);
        }
    }

    private void saveOrderGoods(Long orderId, String orderNo, String activityId,
                                 String orderGoodsStr, String deviceId, Long userId) {
        if (orderGoodsStr == null || orderGoodsStr.isEmpty()) {
            log.info("订单商品信息为空,跳过保存");
            return;
        }

        try {
            JSONArray goodsArray = JSON.parseArray(orderGoodsStr);
            if (goodsArray == null || goodsArray.isEmpty()) {
                return;
            }

            // 如果 ORC_RESULT 已写入占位商品记录，先删除再以回调数据重写
            Long existingCount = orderGoodsMapper.selectCount(
                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<OrderGoods>()
                            .eq(OrderGoods::getOrderId, orderId));
            if (existingCount != null && existingCount > 0) {
                log.info("订单已有 {} 条商品记录，先清除再以回调数据重写 - orderId: {}", existingCount, orderId);
                orderGoodsMapper.delete(
                        new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<OrderGoods>()
                                .eq(OrderGoods::getOrderId, orderId));
            }

            for (int i = 0; i < goodsArray.size(); i++) {
                JSONObject goodsJson = goodsArray.getJSONObject(i);
                OrderGoods goods = new OrderGoods();
                goods.setOrderId(orderId);
                goods.setOrderNo(orderNo);
                goods.setActivityId(activityId);
                goods.setDeviceId(deviceId);
                goods.setUserId(userId);
                goods.setCreateTime(LocalDateTime.now());

                goods.setProductId(goodsJson.getLong("id"));
                goods.setCode(goodsJson.getString("code"));
                goods.setBarCode(goodsJson.getString("bar_code"));
                goods.setProductName(goodsJson.getString("product_name"));
                // 处理图片链接:如果已经是完整 URL 则保持不变,否则添加域名前缀
                String picUrl = goodsJson.getString("pic");
                goods.setPic(normalizeImageUrl(picUrl));
                goods.setProductNum(goodsJson.getInteger("product_num"));

                if (goodsJson.get("money") != null) {
                    goods.setMoney(new BigDecimal(goodsJson.getString("money")));
                }
                if (goodsJson.get("origin_price") != null) {
                    goods.setOriginPrice(goodsJson.getBigDecimal("origin_price"));
                }
                if (goodsJson.get("price") != null) {
                    goods.setPrice(goodsJson.getBigDecimal("price"));
                }

                orderGoodsMapper.insert(goods);
            }

            log.info("订单商品保存成功 - orderId: {}, 商品数量: {}", orderId, goodsArray.size());

        } catch (Exception e) {
            log.error("保存订单商品失败 - orderId: {}", orderId, e);
        }
    }

    /**
     * 标准化图片 URL
     * 如果图片链接已经是完整的 URL(包含 http://或 https://),则保持不变
     * 否则添加配置的图片域名前缀
     *
     * @param picUrl 原始图片链接
     * @return 标准化后的图片链接
     */
    private String normalizeImageUrl(String picUrl) {
        if (picUrl == null || picUrl.isEmpty()) {
            return picUrl;
        }

        // 如果已经是完整的 URL(以 http://或 https://开头),则直接返回
        if (picUrl.startsWith("http://") || picUrl.startsWith("https://")) {
            return picUrl;
        }

        // 否则添加域名前缀
        return commonConfig.getImageDomainPrefix() + picUrl;
    }

    private Order fetchRecognitionAndCreateOrder(String activityId, String deviceId, String userId) {
        try {
            Map<String, Object> recognizeResult = hahaClient.getOrderApi().getRecognizeResult(null, activityId);
            if (recognizeResult != null) {
                String skuListStr = extractParam(recognizeResult, "sku_list");
                String resultStr = extractParam(recognizeResult, "result");
                String resourceInfoStr = extractParam(recognizeResult, "resource_info");
                BigDecimal confidence = parseBigDecimal(recognizeResult.get("confidence"));
                // 以 result.data 为准构建商品清单
                String effectiveSkuListStr = buildSkuListFromResultData(resultStr, skuListStr);

                // 提前从 Redis 读取支付分预授权信息
                String payScoreOrderId = null;
                String payScoreState = null;
                String serviceId = null;
                if (payScoreService != null && deviceId != null && userId != null) {
                    Long uid = Long.parseLong(userId);
                    com.alibaba.fastjson2.JSONObject preAuth = ((com.haha.service.payment.payscore.impl.PayScoreServiceImpl) payScoreService)
                            .readPreAuthFromRedis(deviceId, uid);
                    if (preAuth != null) {
                        // 第一层：Redis 命中
                        payScoreOrderId = preAuth.getString("payScoreOrderId");
                        payScoreState = preAuth.getString("payScoreState");
                        serviceId = preAuth.getString("serviceId");
                    } else {
                        // 第二层：尝试从 DoorRecord（DB 持久化）恢复
                        DoorRecord dr = doorRecordService.getByActivityId(activityId);
                        if (dr != null && dr.getPayScoreOrderId() != null) {
                            payScoreOrderId = dr.getPayScoreOrderId();
                            payScoreState = PayScoreState.CREATED.getCode();
                            serviceId = dr.getServiceId();
                            log.info("从DoorRecord恢复支付分订单ID - userId: {}, deviceId: {}, payScoreOrderId: {}",
                                    userId, deviceId, payScoreOrderId);
                        } else {
                            // 第三层：兜底从追踪SET恢复
                            payScoreOrderId = ((com.haha.service.payment.payscore.impl.PayScoreServiceImpl) payScoreService)
                                    .tryRecoverPayScoreOrderId(uid, deviceId);
                            if (payScoreOrderId != null) {
                                payScoreState = PayScoreState.CREATED.getCode();
                                serviceId = payScoreService.getServiceId();
                                log.info("从追踪SET恢复支付分订单ID成功 - userId: {}, deviceId: {}, payScoreOrderId: {}",
                                        userId, deviceId, payScoreOrderId);
                            } else {
                                log.warn("Redis/DoorRecord/追踪SET 均未找到支付分信息 - userId: {}, deviceId: {}",
                                        userId, deviceId);
                            }
                        }
                    }
                }

                Order order = orderService.createOrderFromRecognition(
                    activityId, deviceId, userId, effectiveSkuListStr, resourceInfoStr, confidence,
                    payScoreOrderId, payScoreState, serviceId);

                if (order != null) {
                    log.info("主动创建订单成功 - orderId: {}, activityId: {}", order.getId(), activityId);
                    tryApplyPreAuthPayScore(order);
                }
                return order;
            }
        } catch (Exception e) {
            log.error("主动查询识别结果失败 - activityId: {}", activityId, e);
        }
        return null;
    }

    private String parseVideoUrl(String resourceInfoStr) {
        if (resourceInfoStr != null && !resourceInfoStr.isEmpty()) {
            JSONObject resourceInfo = JSON.parseObject(resourceInfoStr);
            return resourceInfo.getString("video_url");
        }
        return null;
    }

    /**
     * 记录识别结果详情
     *
     * @param resultStr      识别结果字符串
     * @param skuListStr     商品列表字符串
     * @param resourceInfoStr 资源信息字符串
     */
    private void logRecognizeResultDetails(String resultStr, String skuListStr, String resourceInfoStr) {
        if (resultStr != null && !resultStr.isEmpty()) {
            JSONObject result = JSON.parseObject(resultStr);
            String type = result.getString("type");
            JSONObject data = result.getJSONObject("data");
            JSONArray excepts = result.getJSONArray("excepts");

            log.info("识别类型: {}, 数据: {}", type, data != null ? data.size() : 0);

            if (excepts != null && !excepts.isEmpty()) {
                log.warn("识别结果包含异常: {}", excepts.toJSONString());
            }
        }

        if (skuListStr != null && !skuListStr.isEmpty()) {
            JSONArray skuList = JSON.parseArray(skuListStr);
            log.info("商品数量: {}", skuList != null ? skuList.size() : 0);
        }

        if (resourceInfoStr != null && !resourceInfoStr.isEmpty()) {
            JSONObject resourceInfo = JSON.parseObject(resourceInfoStr);
            String videoUrl = resourceInfo.getString("video_url");
            log.info("视频URL: {}", videoUrl);
        }
    }

    /**
     * 从信号值/延迟值数组中提取最新的值
     * 数据格式：[{"occur_time": 1573401600, "value": "1"}, ...]
     */
    private Integer extractLatestValue(List<Map<String, Object>> values) {
        if (values == null || values.isEmpty()) {
            return null;
        }
        try {
            // 取最后一个（最新的）
            Map<String, Object> latest = values.get(values.size() - 1);
            Object val = latest.get("value");
            if (val != null) {
                return Integer.valueOf(val.toString());
            }
        } catch (Exception e) {
            log.warn("解析信号值失败", e);
        }
        return null;
    }

    /**
     * 从识别结果中提取类型
     * 识别结果格式：{"type":"OUT","data":{...}} 或 {"type":"IN","data":{...}}
     * OUT-拿出(正常消费)，IN-放入(异物放入)
     *
     * @param resultStr 识别结果JSON字符串
     * @return 类型字符串(OUT/IN)，默认返回OUT
     */
    private String extractRecognizeType(String resultStr) {
        if (resultStr == null || resultStr.isEmpty()) {
            return RecognizeActionType.OUT.getCode();
        }
        try {
            JSONObject result = JSON.parseObject(resultStr);
            String type = result.getString("type");
            if (RecognizeActionType.IN.getCode().equalsIgnoreCase(type)) {
                log.info("识别结果类型为IN(放入) - 非正常消费订单");
                return RecognizeActionType.IN.getCode();
            }
            return RecognizeActionType.OUT.getCode();
        } catch (Exception e) {
            log.warn("解析识别结果类型失败，默认为OUT", e);
            return RecognizeActionType.OUT.getCode();
        }
    }

    private Integer parseInteger(Object value) {
        if (value == null) {
            return null;
        }
        try {
            switch (value) {
                case Integer i -> {
                    return i;
                }
                case String str -> {
                    if (str.trim().isEmpty()) {
                        return null;
                    }
                    return Integer.parseInt(str);
                }
                case Number number -> {
                    return number.intValue();
                }
                default -> {
                }
            }
        } catch (NumberFormatException e) {
            log.warn("无法转换为整型: {}", value);
        }
        return null;
    }

    private BigDecimal parseBigDecimal(Object value) {
        if (value == null) {
            return null;
        }
        try {
            return new BigDecimal(value.toString());
        } catch (NumberFormatException e) {
            log.warn("无法转换为 BigDecimal: {}", value);
            return null;
        }
    }

    /**
     * 处理优惠券扣减
     *
     * 业务流程：
     * 1. 查询用户所有可用优惠券（未使用且未过期）
     * 2. 按到期时间升序排序（优先使用最先到期的券）
     * 3. 遍历优惠券，检查是否符合使用条件：
     *    - 订单金额是否达到满减门槛
     *    - 优惠券适用范围是否匹配（全场/指定门店/指定商品）
     * 4. 使用第一张符合条件的优惠券
     * 5. 计算优惠后的最终金额
     * 6. 标记优惠券为已使用
     *
     * 注意：
     * - 一次订单只能使用一张优惠券
     * - 优惠金额不能超过订单金额
     * - 如果设备端已传来优惠后金额，不再使用优惠券
     *
     * @param order 订单对象
     * @param orderMoney 设备端传来的订单金额（可能已包含营销活动优惠）
     * @return 最终实付金额（优惠后）
     */
    private BigDecimal processCouponDiscount(Order order, Object orderMoney) {
        try {
            // 1. 获取订单金额
            BigDecimal originalAmount = orderMoney != null ? new BigDecimal(orderMoney.toString()) : order.getTotalAmount();
            if (originalAmount == null || originalAmount.compareTo(BigDecimal.ZERO) <= 0) {
                log.debug("订单金额为0或空，不使用优惠券 - orderId: {}", order.getId());
                return originalAmount != null ? originalAmount : BigDecimal.ZERO;
            }

            // 2. 查询用户所有可用优惠券
            Long userId = order.getUserId();
            if (userId == null) {
                log.warn("订单用户ID为空，无法使用优惠券 - orderId: {}", order.getId());
                return originalAmount;
            }

            List<UserCoupon> availableCoupons = userCouponService.getAvailableCoupons(userId);
            if (availableCoupons == null || availableCoupons.isEmpty()) {
                log.debug("用户没有可用优惠券 - userId: {}, orderId: {}", userId, order.getId());
                return originalAmount;
            }

            log.info("用户有 {} 张可用优惠券，开始筛选 - userId: {}, orderId: {}",
                    availableCoupons.size(), userId, order.getId());

            // 3. 按到期时间升序排序（优先使用最先到期的券）
            availableCoupons.sort((c1, c2) -> {
                if (c1.getValidEndTime() == null) return 1;
                if (c2.getValidEndTime() == null) return -1;
                return c1.getValidEndTime().compareTo(c2.getValidEndTime());
            });

            // 4. 遍历优惠券，找到第一张符合条件的
            UserCoupon selectedCoupon = null;
            BigDecimal discountAmount = BigDecimal.ZERO;

            for (UserCoupon coupon : availableCoupons) {
                // 填充优惠券模板信息
                UserCoupon couponWithTemplate = userCouponService.getDetail(coupon.getId(), userId);
                if (couponWithTemplate == null) {
                    continue;
                }

                // 检查是否符合使用条件
                if (isCouponApplicable(couponWithTemplate, order, originalAmount)) {
                    selectedCoupon = couponWithTemplate;
                    discountAmount = calculateCouponDiscount(couponWithTemplate, originalAmount);
                    log.info("找到符合条件的优惠券 - couponId: {}, couponName: {}, discountAmount: {}",
                            coupon.getId(), couponWithTemplate.getCouponName(), discountAmount);
                    break; // 只使用一张优惠券
                }
            }

            // 5. 如果没有找到符合条件的优惠券，返回原金额
            if (selectedCoupon == null || discountAmount.compareTo(BigDecimal.ZERO) <= 0) {
                log.debug("没有符合条件的优惠券 - orderId: {}", order.getId());
                return originalAmount;
            }

            // 6. 确保优惠金额不超过订单金额
            if (discountAmount.compareTo(originalAmount) > 0) {
                discountAmount = originalAmount;
                log.info("优惠金额超过订单金额，调整为订单金额 - discountAmount: {}", discountAmount);
            }

            // 7. 计算最终实付金额
            BigDecimal finalAmount = originalAmount.subtract(discountAmount);
            if (finalAmount.compareTo(BigDecimal.ZERO) < 0) {
                finalAmount = BigDecimal.ZERO;
            }

            // 8. 使用优惠券（标记为已使用）
            try {
                userCouponService.useCoupon(selectedCoupon.getId(), userId, order.getId(), discountAmount);
                log.info("优惠券使用成功 - couponId: {}, orderId: {}, discountAmount: {}, finalAmount: {}",
                        selectedCoupon.getId(), order.getId(), discountAmount, finalAmount);
            } catch (Exception e) {
                log.error("优惠券使用失败 - couponId: {}, orderId: {}", selectedCoupon.getId(), order.getId(), e);
                // 如果优惠券使用失败，仍然返回原金额
                return originalAmount;
            }

            // 9. 更新订单的优惠金额和实付金额
            order.setDiscountAmount(discountAmount);
            order.setPaidAmount(finalAmount);
            orderService.updateById(order);

            log.info("订单优惠金额已更新 - orderId: {}, totalAmount: {}, discountAmount: {}, paidAmount: {}",
                    order.getId(), originalAmount, discountAmount, finalAmount);

            return finalAmount;

        } catch (Exception e) {
            log.error("处理优惠券扣减异常 - orderId: {}", order.getId(), e);
            // 异常时返回原金额，不影响正常流程
            return orderMoney != null ? new BigDecimal(orderMoney.toString()) : order.getTotalAmount();
        }
    }

    /**
     * 检查优惠券是否适用于当前订单
     *
     * @param coupon 优惠券信息（包含模板信息）
     * @param order 订单信息
     * @param orderAmount 订单金额
     * @return 是否适用
     */
    private boolean isCouponApplicable(UserCoupon coupon, Order order, BigDecimal orderAmount) {
        try {
            // 1. 检查订单金额是否达到满减门槛
            if (coupon.getMinAmount() != null && orderAmount.compareTo(coupon.getMinAmount()) < 0) {
                log.debug("订单金额未达到优惠券门槛 - couponId: {}, minAmount: {}, orderAmount: {}",
                        coupon.getId(), coupon.getMinAmount(), orderAmount);
                return false;
            }

            // 2. 检查适用范围
            Integer applyScope = coupon.getApplyScope();
            if (applyScope == null || applyScope == ApplyScopeEnum.ALL_SHOP.getCode()) {
                // 全场通用，直接返回true
                return true;
            }

            if (applyScope == ApplyScopeEnum.SPECIFIED_SHOP.getCode()) {
                // 指定门店范围，检查订单设备所属门店
                // 这里简化处理，假设所有门店都适用
                // TODO: 根据实际业务需求，检查订单设备是否属于优惠券指定门店
                log.debug("优惠券为指定门店范围，当前简化处理为适用 - couponId: {}", coupon.getId());
                return true;
            }

            return true;

        } catch (Exception e) {
            log.error("检查优惠券适用性异常 - couponId: {}", coupon.getId(), e);
            return false;
        }
    }

    /**
     * 计算优惠券优惠金额
     *
     * 根据优惠券类型计算：
     * - type=1 (满减券): 直接返回discountValue
     * - type=2 (折扣券): orderAmount * (1 - discountValue/10)
     * - type=3 (抵扣券): 直接返回discountValue
     * - type=4 (兑换券): 不适用，返回0
     *
     * @param coupon 优惠券信息
     * @param orderAmount 订单金额
     * @return 优惠金额
     */
    private BigDecimal calculateCouponDiscount(UserCoupon coupon, BigDecimal orderAmount) {
        try {
            Integer couponType = coupon.getCouponType();
            BigDecimal discountValue = coupon.getDiscountValue();

            if (discountValue == null || discountValue.compareTo(BigDecimal.ZERO) <= 0) {
                return BigDecimal.ZERO;
            }

            if (couponType == CouponTypeEnum.FULL_REDUCTION.getCode() || couponType == CouponTypeEnum.INSTANT_REDUCTION.getCode()) {
                // 直接返回优惠金额
                return discountValue;
            }

            if (couponType == CouponTypeEnum.DISCOUNT.getCode()) {
                // discountValue 表示折扣，如8表示8折
                // 优惠金额 = orderAmount * (1 - discountValue/10)
                BigDecimal discount = discountValue.divide(PaymentConstants.DISCOUNT_DIVISOR, 2, RoundingMode.HALF_UP);
                BigDecimal discountAmount = orderAmount.multiply(BigDecimal.ONE.subtract(discount));

                // 检查是否有最大优惠限制（需要从CouponTemplate获取）
                try {
                    CouponTemplate template = couponTemplateService.getById(coupon.getTemplateId());
                    if (template != null && template.getMaxDiscount() != null &&
                        template.getMaxDiscount().compareTo(BigDecimal.ZERO) > 0) {
                        if (discountAmount.compareTo(template.getMaxDiscount()) > 0) {
                            discountAmount = template.getMaxDiscount();
                        }
                    }
                } catch (Exception e) {
                    log.warn("获取优惠券模板最大优惠限制失败 - templateId: {}", coupon.getTemplateId(), e);
                }

                return discountAmount;
            }

            if (couponType == CouponTypeEnum.PRODUCT.getCode()) {
                // 兑换券不用于金额扣减
                return BigDecimal.ZERO;
            }

            log.warn("未知的优惠券类型 - couponType: {}", couponType);
            return BigDecimal.ZERO;

        } catch (Exception e) {
            log.error("计算优惠券优惠金额异常 - couponId: {}", coupon.getId(), e);
            return BigDecimal.ZERO;
        }
    }

    /**
     * 构建后付费项目列表（post_payments）
     * 从 order_goods 表查询已保存的商品，按微信支付分智慧零售规范构建
     * name="商品信息", description=商品名, amount=单价(元), count=数量
     */
    private List<PayScoreCreateRequest.PostPayment> buildPostPayments(Long orderId, BigDecimal totalAmount) {
        List<PayScoreCreateRequest.PostPayment> payments = new ArrayList<>();
        try {
            List<OrderGoods> goodsList = orderGoodsMapper.selectList(
                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<OrderGoods>()
                            .eq(OrderGoods::getOrderId, orderId));

            if (goodsList == null || goodsList.isEmpty()) {
                log.warn("未找到订单商品信息，使用订单金额构建fallback post_payments - orderId: {}", orderId);
                Order order = orderService.getById(orderId);
                if (order != null) {
                    PayScoreCreateRequest.PostPayment payment = new PayScoreCreateRequest.PostPayment();
                    payment.setName("商品信息");
                    payment.setAmount(order.getTotalAmount() != null ? order.getTotalAmount() : BigDecimal.ZERO);
                    payment.setDescription(order.getOrderName() != null ? order.getOrderName() : "商品");
                    payment.setCount(1);
                    payments.add(payment);
                }
                return payments;
            }

            for (OrderGoods goods : goodsList) {
                PayScoreCreateRequest.PostPayment payment = new PayScoreCreateRequest.PostPayment();
                payment.setName("商品信息");
                // amount 为行总金额（单价 × 数量），微信校验: total_amount = sum(post_payments[].amount)
                BigDecimal unitPrice = goods.getPrice() != null ? goods.getPrice()
                        : (goods.getMoney() != null ? goods.getMoney() : BigDecimal.ZERO);
                int quantity = goods.getProductNum() != null ? goods.getProductNum() : 1;
                payment.setAmount(unitPrice.multiply(BigDecimal.valueOf(quantity)));
                payment.setDescription(goods.getProductName() != null ? goods.getProductName() : "未知商品");
                payment.setCount(1);
                payments.add(payment);
            }

            // 修正四舍五入累计误差：确保 sum(post_payments) 严格等于 totalAmount
            if (totalAmount != null && !payments.isEmpty()) {
                BigDecimal sum = payments.stream()
                        .map(PayScoreCreateRequest.PostPayment::getAmount)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                BigDecimal diff = totalAmount.subtract(sum);
                if (diff.compareTo(BigDecimal.ZERO) != 0) {
                    PayScoreCreateRequest.PostPayment last = payments.get(payments.size() - 1);
                    last.setAmount(last.getAmount().add(diff));
                    log.info("post_payments余额平账: 商品sum={}, 订单total={}, 差额={}, 调整末项amount为{}",
                            sum, totalAmount, diff, last.getAmount());
                }
            }

            log.info("构建post_payments成功 - orderId: {}, 商品数量: {}", orderId, payments.size());
        } catch (Exception e) {
            log.error("构建post_payments失败 - orderId: {}", orderId, e);
        }
        return payments;
    }

    /**
     * 处理支付分扣费
     * 如果订单使用了微信支付分,调用支付分完结接口进行扣费
     *
     * @param order 订单对象
     * @param orderMoney 订单金额
     */
    private void processPayScorePayment(Order order, Object orderMoney) {
        // 检查是否为支付分订单（payChannel 可能因并发回调被覆盖，同时检查 payScoreOrderId）
        boolean isPayScore = PaymentChannel.WECHAT_PAYSCORE.getCode().equals(order.getPayChannel())
                || order.getPayScoreOrderId() != null;
        if (!isPayScore) {
            // 最后一层兜底：尝试从 Redis 追踪SET 恢复支付分订单ID
            if (payScoreService != null && order.getUserId() != null && order.getDeviceId() != null) {
                String recovered = ((com.haha.service.payment.payscore.impl.PayScoreServiceImpl) payScoreService)
                        .tryRecoverPayScoreOrderId(order.getUserId(), order.getDeviceId());
                if (recovered != null) {
                    order.setPayScoreOrderId(recovered);
                    order.setPayChannel(PaymentChannel.WECHAT_PAYSCORE.getCode());
                    order.setPayType("微信支付分");
                    order.setPayScoreState(PayScoreState.CREATED.getCode());
                    String svcId = payScoreService.getServiceId();
                    if (svcId != null) {
                        order.setServiceId(svcId);
                    }
                    // 回写到数据库
                    orderService.lambdaUpdate()
                            .set(Order::getPayScoreOrderId, recovered)
                            .set(Order::getPayChannel, PaymentChannel.WECHAT_PAYSCORE.getCode())
                            .set(Order::getPayType, "微信支付分")
                            .set(Order::getPayScoreState, PayScoreState.CREATED.getCode())
                            .set(svcId != null, Order::getServiceId, svcId)
                            .eq(Order::getId, order.getId())
                            .update();
                    log.info("支付分订单ID已从追踪SET恢复 - orderId: {}, payScoreOrderId: {}",
                            order.getId(), recovered);
                } else {
                    log.debug("订单未使用支付分,跳过扣费 - orderId: {}, payChannel: {}",
                            order.getId(), order.getPayChannel());
                    return;
                }
            } else {
                log.debug("订单未使用支付分,跳过扣费 - orderId: {}, payChannel: {}",
                        order.getId(), order.getPayChannel());
                return;
            }
        }

        // 补全可能丢失的 payChannel
        if (order.getPayChannel() == null) {
            order.setPayChannel(PaymentChannel.WECHAT_PAYSCORE.getCode());
            order.setPayType("微信支付分");
        }

        // 检查支付分服务是否可用
        if (payScoreService == null) {
            log.warn("支付分服务未初始化,无法处理扣费 - orderId: {}", order.getId());
            return;
        }

        // 检查是否已创建支付分服务订单
        if (order.getPayScoreOrderId() == null) {
            log.warn("订单未创建支付分服务订单,无法扣费 - orderId: {}", order.getId());
            return;
        }

        // 检查订单状态,避免重复扣费
        if (PayScoreState.isFinished(order.getPayScoreState())) {
            log.info("订单已完结或已取消,跳过扣费 - orderId: {}, payScoreState: {}",
                    order.getId(), order.getPayScoreState());
            return;
        }

        try {
            BigDecimal totalAmount = orderMoney != null ? new BigDecimal(orderMoney.toString()) : order.getTotalAmount();

            log.info("开始处理支付分扣费 - orderId: {}, payScoreOrderId: {}, amount: {}元",
                    order.getId(), order.getPayScoreOrderId(), totalAmount);

            List<PayScoreCreateRequest.PostPayment> payments = buildPostPayments(order.getId(), totalAmount);

            PayScoreResult result = payScoreService.completePayScoreOrder(order.getId(), totalAmount, payments);

            if (result.isSuccess()) {
                String state = result.getState();
                if (PayScoreState.DONE.getCode().equals(state)) {
                    log.info("支付分扣费成功 - orderId: {}, state: {}, outOrderNo: {}",
                            order.getId(), state, result.getOutOrderNo());
                    // 通知哈哈平台支付已完成
                    syncPayStatusToHaha(order);

                    // 支付成功后扣减库存
                    try {
                        orderInventoryService.decreaseStockOnPaid(order);
                    } catch (Exception e) {
                        log.error("支付分扣费后扣减库存异常: orderNo={}", order.getOrderNo(), e);
                    }
                } else if (PayScoreState.isPaying(state)) {
                    log.warn("支付分扣款已发起但用户待支付 - orderId: {}, state: {}, outOrderNo: {}",
                            order.getId(), state, result.getOutOrderNo());
                } else {
                    log.info("支付分扣费完成 - orderId: {}, state: {}, outOrderNo: {}",
                            order.getId(), state, result.getOutOrderNo());
                }
            } else {
                log.error("支付分扣费失败 - orderId: {}, errorCode: {}, errorMsg: {}",
                        order.getId(), result.getErrorCode(), result.getErrorMsg());
            }
        } catch (Exception e) {
            log.error("处理支付分扣费异常 - orderId: {}", order.getId(), e);
        }
    }

    /**
     * 同步支付状态到哈哈平台
     * 告知哈哈平台该订单已完成支付
     */
    private void syncPayStatusToHaha(Order order) {
        try {
            // 优先使用哈哈平台自己的 orderId，没有则用本地 orderNo
            String hahaRef = order.getHahaOrderId() != null && !order.getHahaOrderId().isEmpty()
                    ? order.getHahaOrderId() : order.getOrderNo();
            if (hahaRef != null && !hahaRef.isEmpty()) {
                hahaClient.getOrderApi().setPayStatus(hahaRef);
                log.info("支付状态已同步到哈哈平台 - hahaRef: {}, localOrderNo: {}", hahaRef, order.getOrderNo());
            }
        } catch (Exception e) {
            log.error("同步支付状态到哈哈平台失败 - orderNo: {}", order.getOrderNo(), e);
        }
    }

    /**
     * 获取订单图像用于人工审核
     * 当识别置信度低于阈值时拉取开门前后图片
     */
    public Map<String, Object> getOrderMedia(String orderId, String activityId) {
        try {
            return hahaClient.getOrderApi().getOrderMedia(orderId, activityId);
        } catch (Exception e) {
            log.error("获取订单图像失败 - orderId: {}, activityId: {}", orderId, activityId, e);
            return null;
        }
    }

    /**
     * 从哈哈平台查询订单详情（用于对账补偿）
     */
    public Map<String, Object> queryOrderFromHaha(String orderId, String activityId) {
        try {
            return hahaClient.getOrderApi().queryOrder(orderId, activityId);
        } catch (Exception e) {
            log.error("从哈哈平台查询订单失败 - orderId: {}, activityId: {}", orderId, activityId, e);
            return null;
        }
    }

    /**
     * 查询回调投递状态
     */
    public Map<String, Object> getCallbackInfo(String type, String actId) {
        try {
            return hahaClient.getOrderApi().getCallbackInfo(type, actId);
        } catch (Exception e) {
            log.error("查询回调投递状态失败 - type: {}, actId: {}", type, actId, e);
            return null;
        }
    }

    /**
     * 获取静态柜分层识别结果
     */
    public Map<String, Object> getLayerRecognize(String orderId, String activityId) {
        try {
            return hahaClient.getOrderApi().getLayerRecognize(orderId, activityId);
        } catch (Exception e) {
            log.error("获取静态柜分层识别结果失败 - orderId: {}, activityId: {}", orderId, activityId, e);
            return null;
        }
    }
}
