package shop.alien.store.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.alien.entity.store.MerchantPaymentOrder;
import shop.alien.entity.store.StoreBookingTable;
import shop.alien.entity.store.StoreInfo;
import shop.alien.entity.store.UserReservation;
import shop.alien.entity.store.UserReservationTable;
import shop.alien.entity.store.UserReservationOrder;
import shop.alien.entity.store.dto.LifeDiscountCouponStoreFriendDto;
import shop.alien.entity.store.vo.StoreReservationListVo;
import shop.alien.entity.store.StoreBookingBusinessHours;
import shop.alien.entity.store.StoreBookingSettings;
import shop.alien.entity.store.EssentialHolidayComparison;
import shop.alien.entity.store.vo.StoreBusinessInfoVo;
import shop.alien.mapper.StoreReservationMapper;
import shop.alien.mapper.UserReservationTableMapper;
import shop.alien.mapper.EssentialHolidayComparisonMapper;
import shop.alien.store.config.BaseRedisService;
import shop.alien.store.listener.RedisKeyExpirationHandler;
import shop.alien.store.service.*;
import shop.alien.store.strategy.merchantPayment.MerchantPaymentStrategy;
import shop.alien.store.strategy.merchantPayment.MerchantPaymentStrategyFactory;
import shop.alien.store.util.ali.AliSms;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import shop.alien.entity.store.LifeNotice;
import shop.alien.entity.store.LifeUser;
import shop.alien.entity.store.StoreUser;
import shop.alien.mapper.LifeNoticeMapper;
import shop.alien.mapper.LifeUserMapper;
import shop.alien.mapper.StoreUserMapper;
import shop.alien.util.common.JwtUtil;
import shop.alien.util.type.LifeNoticeUtil;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 商家端预约管理 服务实现类
 *
 * @author system
 * @since 2025-01-XX
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMapper, UserReservation> implements StoreReservationService {

    private final UserReservationOrderService userReservationOrderService;
    private final BaseRedisService baseRedisService;
    private final RedisKeyExpirationHandler expirationHandler;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final AliSms aliSms;
    private final StoreInfoService storeInfoService;
    private final StoreBookingTableService storeBookingTableService;
    private final UserReservationTableMapper userReservationTableMapper;
    private final LifeNoticeMapper lifeNoticeMapper;

    private final LifeNoticeUtil lifeNoticeUtil;
    private final LifeUserMapper lifeUserMapper;
    private final StoreUserMapper storeUserMapper;
    private final MerchantPaymentStrategyFactory merchantPaymentStrategyFactory;
    private final StoreBookingBusinessHoursService storeBookingBusinessHoursService;
    private final StoreBookingSettingsService storeBookingSettingsService;
    private final EssentialHolidayComparisonMapper essentialHolidayComparisonMapper;
    private final MerchantPaymentOrderService merchantPaymentOrderService;
    private final LifeDiscountCouponStoreFriendService lifeDiscountCouponStoreFriendService;

    /** 预约状态：已取消 */
    private static final int STATUS_CANCELLED = 3;
    /** Redis key前缀：预订核销 */
    private static final String RESERVATION_VERIFY_PREFIX = "reservation:verify:";

    /**
     * 初始化时注册预订核销过期处理器
     */
    @PostConstruct
    public void initReservationExpirationHandler() {
        // 注册预订核销过期处理器
        expirationHandler.registerHandler(RESERVATION_VERIFY_PREFIX, this::handleExpiredReservationKey);
        log.info("预订核销过期处理器注册完成，前缀: {}", RESERVATION_VERIFY_PREFIX);
    }

    @Override
    public List<StoreReservationListVo> getStoreReservationList(Integer storeId, Integer status, Date dateFrom, Date dateTo,
                                                                 Integer orderStatus, String reservationUserName) {
        log.info("StoreReservationServiceImpl.getStoreReservationList?storeId={}, status={}, dateFrom={}, dateTo={}, orderStatus={}, reservationUserName={}",
                storeId, status, dateFrom, dateTo, orderStatus, reservationUserName);

        if (storeId == null) {
            throw new RuntimeException("门店ID不能为空");
        }

        return baseMapper.getStoreReservationList(storeId, status, dateFrom, dateTo, orderStatus, reservationUserName);
    }

    @Override
    public boolean cancelReservationByStore(Integer reservationId, String cancelReason) {
        log.info("StoreReservationServiceImpl.cancelReservationByStore?reservationId={}&cancelReason={}", reservationId, cancelReason);

        if (reservationId == null) {
            throw new RuntimeException("预约ID不能为空");
        }

        // 查询预约信息
        UserReservation reservation = this.getById(reservationId);
        if (reservation == null) {
            throw new RuntimeException("预约不存在");
        }

        // 检查预约状态，已取消的不能再次取消
        if (reservation.getStatus() != null && reservation.getStatus() == STATUS_CANCELLED) {
            throw new RuntimeException("预约已取消，不能重复取消");
        }

        // 校验取消原因（如果提供，限30字）
        if (cancelReason != null && cancelReason.length() > 30) {
            throw new RuntimeException("取消原因不能超过30字");
        }
        // 如果没有提供取消原因，使用默认值
        if (cancelReason == null || cancelReason.trim().isEmpty()) {
            cancelReason = "商家取消";
        }

        // 查询关联的订单信息
        LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(UserReservationOrder::getReservationId, reservationId);
        UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);

        if (order == null) {
            // 如果没有订单，直接更新预约状态为3（已取消）
            reservation.setStatus(STATUS_CANCELLED);
            reservation.setReason(cancelReason); // 保存取消原因
            boolean updateResult = this.updateById(reservation);
            if (!updateResult) {
                throw new RuntimeException("更新预约状态失败");
            }
            log.info("商家端取消预约成功（无订单），reservationId={}", reservationId);
            
            // 发送短信通知
            sendCancelReservationSms(reservation, cancelReason);
            return true;
        }

        // 判断订单费用类型：0-免费, 1-收费
        Integer orderCostType = order.getOrderCostType();
        if (orderCostType == null) {
            orderCostType = 0;
        }

        if (orderCostType == 0) {
            // 免费订单：更新订单状态为4（已取消），更新预约状态为3（已取消）
            order.setOrderStatus(4); // 4:已取消
            boolean orderUpdateResult = userReservationOrderService.updateById(order);
            if (!orderUpdateResult) {
                throw new RuntimeException("更新订单状态失败");
            }

            reservation.setStatus(STATUS_CANCELLED);
            reservation.setReason(cancelReason); // 保存取消原因
            boolean reservationUpdateResult = this.updateById(reservation);
            if (!reservationUpdateResult) {
                throw new RuntimeException("更新预约状态失败");
            }

            log.info("商家端取消预约成功（免费订单），reservationId={}, orderId={}", reservationId, order.getId());
            
            // 发送短信通知
            sendCancelReservationSms(reservation, cancelReason);
            return true;
        } else if (orderCostType == 1) {
            // 付费订单：调用退款接口，然后更新状态
            try {
                // 调用退款接口（和核销中的退款逻辑一样）
                MerchantPaymentStrategy strategy = merchantPaymentStrategyFactory.getStrategy(order.getPaymentMethod());
                strategy.refund(order.getStoreId(), order.getOutTradeNo(), order.getDepositAmount().toString(), "商家取消预约退款", 1);
                
                // 退款成功后，更新订单状态为已退款（7）
//                order.setOrderStatus(7); // 7:已退款
//                order.setPaymentStatus(2); // 2:已退款
//                order.setRefundAmount(order.getDepositAmount());
//                order.setRefundTime(new Date());
//                order.setRefundReason("商家取消预约退款");
//                order.setRefundType(1); // 1:商家退款
//                boolean orderUpdateResult = userReservationOrderService.updateById(order);
//                if (!orderUpdateResult) {
//                    throw new RuntimeException("更新订单状态失败");
//                }

                // 更新预约状态为已取消（3）
                reservation.setStatus(STATUS_CANCELLED);
                reservation.setReason(cancelReason); // 保存取消原因
                boolean reservationUpdateResult = this.updateById(reservation);
                if (!reservationUpdateResult) {
                    throw new RuntimeException("更新预约状态失败");
                }

                log.info("商家端取消预约成功（付费订单），reservationId={}, orderId={}", reservationId, order.getId());
                
                // 先发送取消通知和短信
                sendCancelReservationSms(reservation, cancelReason);
                
                // 付费订单：再发送订金退款通知和短信
                try {
                    sendDepositRefundSmsAndNoticeInternal(reservation, order);
                } catch (Exception smsEx) {
                    log.error("商家取消预约后发送退款通知/短信失败，不影响取消结果，reservationId={}", reservationId, smsEx);
                }
                
                return true;
            } catch (Exception e) {
                log.error("付费订单取消退款失败，reservationId={}, orderId={}, error={}", 
                        reservationId, order.getId(), e.getMessage(), e);
                throw new RuntimeException("付费订单取消退款失败：" + e.getMessage());
            }
        } else {
            throw new RuntimeException("订单费用类型异常，orderCostType=" + orderCostType);
        }
    }

    /**
     * 发送商家取消预约短信通知
     */
    private void sendCancelReservationSms(UserReservation reservation, String cancelReason) {
        try {
            // 获取用户手机号
            String phone = reservation.getReservationUserPhone();
            if (phone == null || phone.trim().isEmpty()) {
                log.warn("预约人电话为空，无法发送短信通知，reservationId={}", reservation.getId());
                return;
            }

            // 查询店铺信息
            StoreInfo storeInfo = storeInfoService.getById(reservation.getStoreId());
            String storeName = storeInfo != null ? storeInfo.getStoreName() : "未知店铺";
            if (storeName == null || storeName.trim().isEmpty()) {
                storeName = "未知店铺";
            }

            // 查询桌号信息
            LambdaQueryWrapper<UserReservationTable> tableWrapper = new LambdaQueryWrapper<>();
            tableWrapper.eq(UserReservationTable::getReservationId, reservation.getId())
                    .eq(UserReservationTable::getDeleteFlag, 0)
                    .orderByAsc(UserReservationTable::getSort);
            List<UserReservationTable> reservationTables = userReservationTableMapper.selectList(tableWrapper);
            
            String tableNumber = "未知桌号";
            if (reservationTables != null && !reservationTables.isEmpty()) {
                // 获取所有桌号，用逗号分隔
                List<String> tableNumbers = reservationTables.stream()
                        .map(rt -> {
                            StoreBookingTable table = storeBookingTableService.getById(rt.getTableId());
                            return table != null && table.getTableNumber() != null ? table.getTableNumber() : null;
                        })
                        .filter(tn -> tn != null && !tn.trim().isEmpty())
                        .collect(Collectors.toList());
                
                if (!tableNumbers.isEmpty()) {
                    tableNumber = String.join(",", tableNumbers);
                }
            }

            // 格式化预约时间：日期 + 时间（格式：2026-01-01 14:00）
            String dateTime = formatReservationDateTime(reservation);
            
            // 构建通知内容（和短信内容一样）
            // 短信模板：您在${dateTime},预订了${storeName},的${number}桌位,已被商家取消,取消原因:${info},请您重新预订
            String noticeMessage = String.format("您在%s,预订了%s,的%s桌位,已被商家取消,取消原因:%s,请您重新预订", 
                    dateTime, storeName, tableNumber, cancelReason);
            
            // 调用发送短信接口
            Integer smsResult = aliSms.sendCancelReservationSms(phone, dateTime, storeName, tableNumber, cancelReason);
            if (smsResult != null && smsResult == 1) {
                log.info("商家取消预约短信发送成功，reservationId={}, phone={}", reservation.getId(), phone);
            } else {
                log.warn("商家取消预约短信发送失败，reservationId={}, phone={}", reservation.getId(), phone);
            }
            
            // 发送通知（和短信内容一样）
            sendCancelReservationNotice(reservation, noticeMessage);
        } catch (Exception e) {
            // 短信和通知发送失败不影响取消预约流程，只记录日志
            log.error("发送商家取消预约短信和通知异常，reservationId={}", reservation.getId(), e);
        }
    }

    /**
     * 发送商家取消预约通知
     */
    private void sendCancelReservationNotice(UserReservation reservation, String noticeMessage) {
        try {
            // 通过 userId 查询 life_user 表获取手机号
            Integer userId = reservation.getUserId();
            if (userId == null) {
                log.warn("预约用户ID为空，无法发送通知，reservationId={}", reservation.getId());
                return;
            }
            
            // 查询用户信息
            LifeUser lifeUser = lifeUserMapper.selectById(userId);
            if (lifeUser == null) {
                log.warn("未找到用户信息，无法发送通知，reservationId={}, userId={}", reservation.getId(), userId);
                return;
            }
            
            // 获取用户手机号
            String userPhone = lifeUser.getUserPhone();
            if (userPhone == null || userPhone.trim().isEmpty()) {
                log.warn("用户手机号为空，无法发送通知，reservationId={}, userId={}", reservation.getId(), userId);
                return;
            }
            
            // 构建receiverId：用户端使用 "user_" + 手机号
            String receiverId = "user_" + userPhone.trim();
            
            // 构建通知内容JSON
            JSONObject contextJson = new JSONObject();
            contextJson.put("message", noticeMessage);
            contextJson.put("reservationId", reservation.getId());
            contextJson.put("reservationNo", reservation.getReservationNo());
            
            // 创建通知记录
            LifeNotice lifeNotice = new LifeNotice();
            lifeNotice.setSenderId("system");
            lifeNotice.setReceiverId(receiverId);
            lifeNotice.setBusinessId(reservation.getId());
            lifeNotice.setTitle("订单取消提醒");
            lifeNotice.setContext(contextJson.toJSONString());
            lifeNotice.setNoticeType(2); // 2-订单提醒
            lifeNotice.setIsRead(0);
            
            // 保存通知到数据库
            lifeNoticeUtil.fillUserTypeAndRefId(lifeNotice);
            lifeNoticeMapper.insert(lifeNotice);
            
            log.info("商家取消预约通知发送成功，reservationId={}, userId={}, receiverId={}", 
                    reservation.getId(), userId, receiverId);
        } catch (Exception e) {
            // 通知发送失败不影响流程，只记录日志
            log.error("发送商家取消预约通知异常，reservationId={}, userId={}", 
                    reservation.getId(), reservation.getUserId(), e);
        }
    }

    @Override
    public boolean sendDepositRefundSmsAndNotice(Integer reservationId) {
        log.info("StoreReservationServiceImpl.sendDepositRefundSmsAndNotice?reservationId={}", reservationId);

        if (reservationId == null) {
            throw new RuntimeException("预约ID不能为空");
        }

        // 查询预约信息
        UserReservation reservation = this.getById(reservationId);
        if (reservation == null) {
            throw new RuntimeException("预约不存在");
        }

        // 查询关联的订单信息
        LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(UserReservationOrder::getReservationId, reservationId);
        UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);

        if (order == null) {
            throw new RuntimeException("预约订单不存在");
        }

        try {
            sendDepositRefundSmsAndNoticeInternal(reservation, order);
            return true;
        } catch (Exception e) {
            log.error("发送订金退款短信和通知失败，reservationId={}", reservationId, e);
            throw new RuntimeException("发送订金退款短信和通知失败：" + e.getMessage());
        }
    }

    /**
     * 发送订金退款短信和通知（内部方法）
     *
     * @param reservation 预约信息
     * @param order 订单信息
     */
    private void sendDepositRefundSmsAndNoticeInternal(UserReservation reservation, UserReservationOrder order) {
        try {
            // 获取用户手机号
            String userPhone = reservation.getReservationUserPhone();
            if (!StringUtils.hasText(userPhone)) {
                log.warn("核销后发送订金退款短信和通知，用户手机号为空，无法发送，reservationId={}", reservation.getId());
                return;
            }

            // 短信和通知内容
            String message = "您预付的订金已返还至您的支付账号,请注意查收.";

            // 发送短信
            Integer smsResult = aliSms.sendDepositRefundSms(userPhone);
            if (smsResult != null && smsResult == 1) {
                log.info("核销后订金退款短信发送成功，reservationId={}, phone={}", reservation.getId(), userPhone);
            } else {
                log.warn("核销后订金退款短信发送失败，reservationId={}, phone={}", reservation.getId(), userPhone);
            }

            // 发送通知
            sendDepositRefundNotice(reservation, message);
        } catch (Exception e) {
            log.error("发送订金退款短信和通知异常，reservationId={}", reservation.getId(), e);
            throw e;
        }
    }

    /**
     * 发送订金退款通知
     *
     * @param reservation 预约信息
     * @param noticeMessage 通知内容
     */
    private void sendDepositRefundNotice(UserReservation reservation, String noticeMessage) {
        try {
            // 通过 userId 查询 life_user 表获取手机号
            Integer userId = reservation.getUserId();
            if (userId == null) {
                log.warn("预约用户ID为空，无法发送通知，reservationId={}", reservation.getId());
                return;
            }
            
            // 查询用户信息
            LifeUser lifeUser = lifeUserMapper.selectById(userId);
            if (lifeUser == null) {
                log.warn("未找到用户信息，无法发送通知，reservationId={}, userId={}", reservation.getId(), userId);
                return;
            }
            
            // 获取用户手机号
            String userPhone = lifeUser.getUserPhone();
            if (userPhone == null || userPhone.trim().isEmpty()) {
                log.warn("用户手机号为空，无法发送通知，reservationId={}, userId={}", reservation.getId(), userId);
                return;
            }
            
            // 构建receiverId：用户端使用 "user_" + 手机号
            String receiverId = "user_" + userPhone.trim();
            
            // 构建通知内容JSON
            JSONObject contextJson = new JSONObject();
            contextJson.put("message", noticeMessage);
            contextJson.put("reservationId", reservation.getId());
            contextJson.put("reservationNo", reservation.getReservationNo());
            
            // 创建通知记录
            LifeNotice lifeNotice = new LifeNotice();
            lifeNotice.setSenderId("system");
            lifeNotice.setReceiverId(receiverId);
            lifeNotice.setBusinessId(reservation.getId());
            lifeNotice.setTitle("退款成功通知");
            lifeNotice.setContext(contextJson.toJSONString());
            lifeNotice.setNoticeType(2); // 2-订单提醒
            lifeNotice.setIsRead(0);
            
            // 保存通知到数据库
            lifeNoticeUtil.fillUserTypeAndRefId(lifeNotice);
            lifeNoticeMapper.insert(lifeNotice);
            
            log.info("核销后订金退款通知发送成功，reservationId={}, userId={}, receiverId={}", 
                    reservation.getId(), userId, receiverId);
        } catch (Exception e) {
            log.error("发送订金退款通知异常，reservationId={}, userId={}", 
                    reservation.getId(), reservation.getUserId(), e);
            throw e;
        }
    }

    /**
     * 格式化预约时间：日期 + 时间（格式：2026-01-01 14:00）
     */
    private String formatReservationDateTime(UserReservation reservation) {
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            
            String dateStr = "";
            if (reservation.getReservationDate() != null) {
                dateStr = dateFormat.format(reservation.getReservationDate());
            }
            
            String timeStr = "";
            if (reservation.getStartTime() != null && !reservation.getStartTime().trim().isEmpty()) {
                timeStr = reservation.getStartTime();
            }
            
            if (dateStr.isEmpty() && timeStr.isEmpty()) {
                return "未知时间";
            } else if (dateStr.isEmpty()) {
                return timeStr;
            } else if (timeStr.isEmpty()) {
                return dateStr;
            } else {
                return dateStr + " " + timeStr;
            }
        } catch (Exception e) {
            log.error("格式化预约时间失败，reservationId={}", reservation.getId(), e);
            return "未知时间";
        }
    }

    @Override
    public boolean deleteReservationByStore(Integer reservationId) {
        log.info("StoreReservationServiceImpl.deleteReservationByStore?reservationId={}", reservationId);

        if (reservationId == null) {
            throw new RuntimeException("预约ID不能为空");
        }

        // 查询预约信息
        UserReservation reservation = this.getById(reservationId);
        if (reservation == null) {
            throw new RuntimeException("预约不存在");
        }

        // 查询关联的订单信息
        LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(UserReservationOrder::getReservationId, reservationId);
        UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);

        if (order == null) {
            // 如果没有订单，直接删除预约记录
            boolean deleteResult = this.removeById(reservationId);
            if (!deleteResult) {
                throw new RuntimeException("删除预约记录失败");
            }
            log.info("商家端删除预订信息成功（无订单），reservationId={}", reservationId);
            return true;
        }

        // 判断订单状态：只有已取消(4)、已退款(7)、已完成(2)、已过期(3)状态才能删除
        Integer orderStatus = order.getOrderStatus();
        if (orderStatus == null) {
            throw new RuntimeException("订单状态异常，无法删除");
        }

        // 定义可删除的订单状态：2:已完成, 3:已过期, 4:已取消, 7:已退款
        boolean canDelete = orderStatus == 2 || orderStatus == 3 || orderStatus == 4 || orderStatus == 7;

        if (!canDelete) {
            String statusText = getOrderStatusText(orderStatus);
            throw new RuntimeException("订单状态为" + statusText + "，不允许删除。只有已取消、已退款、已完成、已过期状态的订单可以删除");
        }

        // 已过期订单：仅当当前时间 > 预约结束时间 + 3小时才允许删除
        validateExpiredReservationDeleteTimeWindow(reservation, orderStatus);

        // 删除订单记录（逻辑删除）
        boolean orderDeleteResult = userReservationOrderService.removeById(order.getId());
        if (!orderDeleteResult) {
            throw new RuntimeException("删除订单记录失败");
        }

        // 删除预约记录（逻辑删除）
        boolean reservationDeleteResult = this.removeById(reservationId);
        if (!reservationDeleteResult) {
            throw new RuntimeException("删除预约记录失败");
        }

        log.info("商家端删除预订信息成功，reservationId={}, orderId={}, orderStatus={}",
                reservationId, order.getId(), orderStatus);
        return true;
    }

    /**
     * 删除校验：已过期状态的预订，需满足“当前时间 > 结束时间 + 3小时”。
     */
    private void validateExpiredReservationDeleteTimeWindow(UserReservation reservation, Integer orderStatus) {
        // 仅校验已过期状态
        if (orderStatus == null || orderStatus != 3) {
            return;
        }

        Date reservationEndTime = parseReservationEndDateTime(reservation);
        if (reservationEndTime == null) {
            throw new RuntimeException("预约结束时间异常，暂不可删除");
        }

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(reservationEndTime);
        calendar.add(Calendar.HOUR_OF_DAY, 3);
        Date endPlusThreeHours = calendar.getTime();

        if (!new Date().after(endPlusThreeHours)) {
            throw new RuntimeException("已过期订单需在结束3小时后才可删除");
        }
    }

    /**
     * 解析预约结束时间，兼容两种场景：
     * 1) endTime 已是完整时间（yyyy-MM-dd HH:mm[:ss]）
     * 2) endTime 仅为时分秒（HH:mm[:ss]），需与 reservationDate 拼接
     */
    private Date parseReservationEndDateTime(UserReservation reservation) {
        if (reservation == null || !StringUtils.hasText(reservation.getEndTime())) {
            return null;
        }
        String endTime = reservation.getEndTime().trim();

        // 场景1：endTime 已是完整日期时间
        String normalizedFullDateTime = normalizeDateTime(endTime);
        if (normalizedFullDateTime != null) {
            try {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                sdf.setLenient(false);
                return sdf.parse(normalizedFullDateTime);
            } catch (ParseException e) {
                log.warn("解析完整结束时间失败，reservationId={}, endTime={}", reservation.getId(), endTime, e);
            }
        }

        // 场景2：endTime 仅时间，拼接 reservationDate 再解析
        if (reservation.getReservationDate() == null) {
            return null;
        }
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String datePart = dateFormat.format(reservation.getReservationDate());
        String mergedDateTime = datePart + " " + endTime;
        String normalizedMergedDateTime = normalizeDateTime(mergedDateTime);
        if (normalizedMergedDateTime == null) {
            return null;
        }
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setLenient(false);
            return sdf.parse(normalizedMergedDateTime);
        } catch (ParseException e) {
            log.warn("解析拼接结束时间失败，reservationId={}, reservationDate={}, endTime={}",
                    reservation.getId(), datePart, endTime, e);
            return null;
        }
    }

    @Override
    public boolean addTimeByStore(Integer reservationId, String addTimeStart, Integer addTimeMinutes) {
        log.info("StoreReservationServiceImpl.addTimeByStore?reservationId={}, addTimeStart={}, addTimeMinutes={}",
                reservationId, addTimeStart, addTimeMinutes);

        if (reservationId == null) {
            throw new RuntimeException("预约ID不能为空");
        }

        if (addTimeStart == null || addTimeStart.trim().isEmpty()) {
            throw new RuntimeException("加时开始时间不能为空");
        }

        if (addTimeMinutes == null || addTimeMinutes <= 0) {
            throw new RuntimeException("加时分钟数必须大于0");
        }

        // 验证并标准化时间格式（支持 yyyy-MM-dd HH:mm:ss 和 yyyy-MM-dd HH:mm）
        String normalizedAddTimeStart = normalizeDateTime(addTimeStart);
        if (normalizedAddTimeStart == null) {
            throw new RuntimeException("加时开始时间格式错误，应为yyyy-MM-dd HH:mm:ss或yyyy-MM-dd HH:mm格式，如：2026-03-11 13:00:00 或 2026-03-11 13:00");
        }

        // 查询预约信息
        UserReservation reservation = this.getById(reservationId);
        if (reservation == null) {
            throw new RuntimeException("预约不存在");
        }

        // 保存原结束时间用于日志
        String oldEndTime = reservation.getEndTime();

        // 标准化预约的结束时间
        String normalizedEndTime = normalizeDateTime(reservation.getEndTime());
        if (normalizedEndTime == null) {
            throw new RuntimeException("预约结束时间格式错误，无法进行加时操作");
        }

        // 计算新的结束时间
        // 无论加时开始时间是否超过预订结束时间，都从预订结束时间开始累加
        // 如果加时开始时间 > 预订结束时间，中间的空挡时间也要累加进去
        // 例如：预订结束时间 12:00，加时开始时间 13:00，加时30分钟
        // 新结束时间 = 加时开始时间 + 加时分钟数 = 13:00 + 30分钟 = 13:30
        // （这已经包含了空挡时间，因为是从加时开始时间开始计算的）
        String newEndTime;
        int timeComparison = compareTime(normalizedAddTimeStart, normalizedEndTime);
        if (timeComparison <= 0) {
            // 加时开始时间 <= 预订结束时间：新结束时间 = 预订结束时间 + 加时分钟数
            newEndTime = calculateNewEndTime(normalizedEndTime, addTimeMinutes);
        } else {
            // 加时开始时间 > 预订结束时间：新结束时间 = 加时开始时间 + 加时分钟数
            // （这已经包含了中间的空挡时间，因为是从加时开始时间开始累加的）
            newEndTime = calculateNewEndTime(normalizedAddTimeStart, addTimeMinutes);
        }

        // 校验：不能超过下一个已确认预约的开始时间
        validateAddTimeNotExceedNextReservation(reservation, normalizedAddTimeStart, newEndTime);

        // 校验：不能超过营业时间的结束时间
        validateAddTimeNotExceedBusinessHours(reservation, normalizedAddTimeStart, newEndTime);

        // 更新预约结束时间
        reservation.setEndTime(newEndTime);
        boolean updateResult = this.updateById(reservation);
        if (!updateResult) {
            throw new RuntimeException("更新预约结束时间失败");
        }

        log.info("商家端加时成功，reservationId={}, 原结束时间={}, 加时开始时间={}, 加时分钟数={}, 新结束时间={}",
                reservationId, oldEndTime, addTimeStart, addTimeMinutes, newEndTime);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean verifyReservationByCode(String verificationCode) {
        log.info("StoreReservationServiceImpl.verifyReservationByCode?verificationCode={}", verificationCode);

        if (verificationCode == null || verificationCode.trim().isEmpty()) {
            throw new RuntimeException("核销码不能为空");
        }

        // 根据核销码查询订单信息
        LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(UserReservationOrder::getVerificationCode, verificationCode)
                .eq(UserReservationOrder::getDeleteFlag, 0)
                .last("LIMIT 1");
        UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);

        if (order == null) {
            throw new RuntimeException("核销码不存在或订单已被删除");
        }

        // 根据订单关联的预约ID查询预约信息
        UserReservation reservation = this.getById(order.getReservationId());
        if (reservation == null) {
            throw new RuntimeException("预约不存在或已被删除");
        }

        // 校验门店：核销码必须属于当前登录的门店
        Integer currentStoreId = getCurrentStoreId();
        if (currentStoreId == null) {
            log.warn("无法获取当前登录门店ID，跳过门店校验");
        } else {
            Integer reservationStoreId = reservation.getStoreId();
            if (reservationStoreId == null || !currentStoreId.equals(reservationStoreId)) {
                log.error("核销码不属于本门店，当前门店ID={}, 预约门店ID={}, verificationCode={}", 
                        currentStoreId, reservationStoreId, verificationCode);
                throw new RuntimeException("此预订码不属于本门店，不予核销");
            }
        }

        // 校验预约状态（必须是已确认状态才能核销）
        if (reservation.getStatus() == null || reservation.getStatus() != 1) {
            throw new RuntimeException("预约状态不正确，只有已确认的预约才能核销");
        }

        // 校验是否过期：当前时间是否超过预约结束时间
        if (isReservationExpired(reservation)) {
            throw new RuntimeException("预约已过期，无法核销");
        }

//        if(order.getOrderCostType() == 1){
//            //调用退款
//            MerchantPaymentStrategy strategy = merchantPaymentStrategyFactory.getStrategy(order.getPaymentMethod());
//            strategy.refund(order.getStoreId(), order.getOutTradeNo(), order.getDepositAmount().toString(), "已扫码到店商家退款", 3);
//
//        }

        // 更新预约状态为已到店（status = 2）
        reservation.setStatus(2); // 已到店
        reservation.setActualArrivalTime(new Date());
        boolean updateReservation = this.updateById(reservation);
        if (!updateReservation) {
            throw new RuntimeException("更新预约状态失败");
        }

        // 更新订单状态为已完成（order_status = 2）
        order.setOrderStatus(2); // 已完成
        boolean updateOrder = userReservationOrderService.updateById(order);
        if (!updateOrder) {
            throw new RuntimeException("更新订单状态失败");
        }

        //发放好友优惠券
//        LifeDiscountCouponStoreFriendDto lifeDiscountCouponStoreFriendDto = new LifeDiscountCouponStoreFriendDto();
//        lifeDiscountCouponStoreFriendDto.setOrderId(Integer.parseInt(order.getId().toString()));
//        lifeDiscountCouponStoreFriendService.newIssueFriendCoupon(lifeDiscountCouponStoreFriendDto);

        // 核销成功后，发送订金退款短信和通知
        try {
            sendDepositRefundSmsAndNoticeInternal(reservation, order);
        } catch (Exception e) {
            log.error("核销后发送订金退款短信和通知失败，但不影响核销流程，verificationCode={}, orderId={}, error={}",
                    verificationCode, order.getId(), e.getMessage(), e);
            // 短信和通知发送失败不影响核销流程，只记录日志
        }


        log.info("核销预约订单成功，verificationCode={}, reservationId={}, orderId={}",
                verificationCode, reservation.getId(), order.getId());
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean refundReservation(Integer reservationId) {
        log.info("StoreReservationServiceImpl.refundReservation?reservationId={}", reservationId);

        if (reservationId == null) {
            throw new RuntimeException("预约ID不能为空");
        }

        // 查询预约信息
        UserReservation reservation = this.getById(reservationId);
        if (reservation == null) {
            throw new RuntimeException("预约不存在");
        }

        // 查询关联的订单
        LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(UserReservationOrder::getReservationId, reservationId)
                .eq(UserReservationOrder::getDeleteFlag, 0)
                .last("LIMIT 1");
        UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);

        if (order == null) {
            throw new RuntimeException("未找到关联的订单信息");
        }

        // 检查订单状态，只有已完成的订单才能退款
        if (order.getOrderStatus() == null || order.getOrderStatus() != 1) {
            log.warn("订单状态不正确，无法退款，订单ID: {}, 订单状态: {}", order.getId(), order.getOrderStatus());
            throw new RuntimeException("订单状态不正确，只有已完成的订单才能退款");
        }

        // 检查订单费用类型，只有付费订单才需要退款
        if (order.getOrderCostType() == null || order.getOrderCostType() == 0) {
            log.info("免费订单无需退款，订单ID: {}, 预约ID: {}", order.getId(), reservationId);
            return true;
        }

        // TODO: 调用支付退款接口
        // 1. 调用第三方支付退款接口（微信支付、支付宝等）
        // 2. 根据退款结果更新订单状态

        // 更新订单状态为已退款（order_status = 7）
        order.setOrderStatus(7); // 已退款
        order.setRefundTime(new Date());
        boolean updateOrder = userReservationOrderService.updateById(order);
        if (!updateOrder) {
            throw new RuntimeException("更新订单状态失败");
        }

        // 更新预约状态为已取消（status = 3）
        reservation.setStatus(3); // 已取消
        boolean updateReservation = this.updateById(reservation);
        if (!updateReservation) {
            throw new RuntimeException("更新预约状态失败");
        }

        log.info("预订退款处理成功，预约ID: {}, 订单ID: {}", reservationId, order.getId());
        return true;
    }

    /**
     * 校验加时不能超过下一个已确认预约的开始时间
     */
    private void validateAddTimeNotExceedNextReservation(UserReservation reservation, String addTimeStart, String newEndTime) {
        // 确定查询基准时间：取当前结束时间和加时开始时间的较大值
        String queryBaseTime = reservation.getEndTime();
        if (compareTime(addTimeStart, reservation.getEndTime()) > 0) {
            queryBaseTime = addTimeStart;
        }

        // 查询同一门店、同一日期、状态为已确认（status=1 2）的预约
        LambdaQueryWrapper<UserReservation> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserReservation::getStoreId, reservation.getStoreId())
                .apply("DATE(user_reservation.reservation_date) = DATE({0})", reservation.getReservationDate())
                .in(UserReservation::getStatus,1,2)
                .ge(UserReservation::getStartTime, queryBaseTime)
                .ne(UserReservation::getId, reservation.getId())
                .eq(UserReservation::getDeleteFlag, 0)
                .orderByAsc(UserReservation::getStartTime)
                .last("LIMIT 1");

        UserReservation nextReservation = this.getOne(wrapper);

        if (nextReservation != null) {
            if (compareTime(newEndTime, nextReservation.getStartTime()) > 0) {
                // 计算最多可加时的分钟数
                int maxAddTimeMinutes = calculateMaxAddTimeMinutes(queryBaseTime, nextReservation.getStartTime());
                throw new RuntimeException("最多可加时" + maxAddTimeMinutes + "分钟");
            }
        }
    }

    /**
     * 标准化时间格式：将 yyyy-MM-dd HH:mm 或 yyyy-MM-dd HH:mm:ss 统一转换为 yyyy-MM-dd HH:mm:ss
     * @param dateTime 时间字符串
     * @return 标准化后的时间字符串（yyyy-MM-dd HH:mm:ss格式），如果格式不正确返回null
     */
    private String normalizeDateTime(String dateTime) {
        if (dateTime == null || dateTime.trim().isEmpty()) {
            return null;
        }
        
        String trimmed = dateTime.trim();
        
        // 尝试解析 yyyy-MM-dd HH:mm:ss 格式
        SimpleDateFormat formatWithSeconds = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        formatWithSeconds.setLenient(false);
        try {
            Date date = formatWithSeconds.parse(trimmed);
            return formatWithSeconds.format(date);
        } catch (ParseException e) {
            // 继续尝试其他格式
        }
        
        // 尝试解析 yyyy-MM-dd HH:mm 格式
        SimpleDateFormat formatWithoutSeconds = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        formatWithoutSeconds.setLenient(false);
        try {
            Date date = formatWithoutSeconds.parse(trimmed);
            // 转换为 yyyy-MM-dd HH:mm:ss 格式（秒数补0）
            return formatWithSeconds.format(date);
        } catch (ParseException e) {
            // 格式不正确
            return null;
        }
    }

    /**
     * 比较两个时间字符串（支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm 格式）
     */
    private int compareTime(String time1, String time2) {
        try {
            // 先标准化时间格式
            String normalizedTime1 = normalizeDateTime(time1);
            String normalizedTime2 = normalizeDateTime(time2);
            
            if (normalizedTime1 == null || normalizedTime2 == null) {
                throw new RuntimeException("时间格式不正确，无法比较");
            }
            
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setLenient(false);
            Date date1 = sdf.parse(normalizedTime1);
            Date date2 = sdf.parse(normalizedTime2);
            return date1.compareTo(date2);
        } catch (ParseException e) {
            log.error("比较时间失败，time1={}, time2={}", time1, time2, e);
            throw new RuntimeException("时间比较失败：" + e.getMessage());
        }
    }

    /**
     * 计算两个时间之间的分钟差（最多可加时的分钟数）
     * @param startTime 开始时间（支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm 格式）
     * @param endTime 结束时间（支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm 格式）
     * @return 分钟差，如果结束时间早于开始时间返回0
     */
    private int calculateMaxAddTimeMinutes(String startTime, String endTime) {
        try {
            // 先标准化时间格式
            String normalizedStartTime = normalizeDateTime(startTime);
            String normalizedEndTime = normalizeDateTime(endTime);
            
            if (normalizedStartTime == null || normalizedEndTime == null) {
                log.warn("时间格式不正确，无法计算分钟差，startTime={}, endTime={}", startTime, endTime);
                return 0;
            }
            
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setLenient(false);
            Date startDate = sdf.parse(normalizedStartTime);
            Date endDate = sdf.parse(normalizedEndTime);
            
            // 如果结束时间早于开始时间，返回0
            if (endDate.before(startDate)) {
                return 0;
            }
            
            // 计算分钟差
            long diffInMillis = endDate.getTime() - startDate.getTime();
            int minutes = (int) (diffInMillis / (1000 * 60));
            
            return minutes;
        } catch (ParseException e) {
            log.error("计算最多可加时分钟数失败，startTime={}, endTime={}", startTime, endTime, e);
            return 0;
        }
    }

    /**
     * 计算新的结束时间：加时开始时间 + 加时分钟数
     * 返回格式：yyyy-MM-dd HH:mm（不含秒）
     */
    private String calculateNewEndTime(String addTimeStart, Integer addTimeMinutes) {
        try {
            // 解析输入时间（支持 yyyy-MM-dd HH:mm:ss 格式）
            SimpleDateFormat parseFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            parseFormat.setLenient(false);
            Date startDate = parseFormat.parse(addTimeStart.trim());

            Calendar calendar = Calendar.getInstance();
            calendar.setTime(startDate);
            calendar.add(Calendar.MINUTE, addTimeMinutes);

            // 返回格式：yyyy-MM-dd HH:mm（不含秒）
            SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            return outputFormat.format(calendar.getTime());
        } catch (ParseException e) {
            log.error("计算新的结束时间失败，addTimeStart={}, addTimeMinutes={}", addTimeStart, addTimeMinutes, e);
            throw new RuntimeException("时间计算失败：" + e.getMessage());
        }
    }

    /**
     * 计算两个时间之间的分钟差（time2 - time1）
     * @param time1 开始时间（yyyy-MM-dd HH:mm:ss格式）
     * @param time2 结束时间（yyyy-MM-dd HH:mm:ss格式）
     * @return 分钟差，如果time2 < time1则返回负数
     */
    private long calculateTimeGapInMinutes(String time1, String time2) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setLenient(false);
            Date date1 = sdf.parse(time1.trim());
            Date date2 = sdf.parse(time2.trim());
            
            long diffInMillis = date2.getTime() - date1.getTime();
            return diffInMillis / (1000 * 60); // 转换为分钟
        } catch (ParseException e) {
            log.error("计算时间差失败，time1={}, time2={}", time1, time2, e);
            throw new RuntimeException("计算时间差失败：" + e.getMessage());
        }
    }

    /**
     * 校验加时不能超过营业时间的结束时间
     * 
     * @param reservation 预约信息
     * @param newEndTime 新的结束时间（yyyy-MM-dd HH:mm:ss格式）
     */
    private void validateAddTimeNotExceedBusinessHours(UserReservation reservation, String addTimeStart, String newEndTime) {
        try {
            if (reservation == null || reservation.getStoreId() == null || reservation.getReservationDate() == null) {
                log.warn("预约信息不完整，跳过营业时间校验");
                return;
            }

            // 查询门店的预订设置
            StoreBookingSettings settings = storeBookingSettingsService.getByStoreId(reservation.getStoreId());
            if (settings == null || settings.getId() == null) {
                log.warn("门店未配置预订设置，跳过营业时间校验，storeId={}", reservation.getStoreId());
                return;
            }

            // 查询营业时间列表
            List<StoreBookingBusinessHours> businessHoursList = storeBookingBusinessHoursService.getListBySettingsId(settings.getId());
            if (businessHoursList == null || businessHoursList.isEmpty()) {
                log.warn("门店未配置营业时间，跳过营业时间校验，storeId={}", reservation.getStoreId());
                return;
            }

            // 解析预约日期
            Date reservationDate = reservation.getReservationDate();
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String reservationDateStr = dateFormat.format(reservationDate);

            // 查找匹配的营业时间
            StoreBookingBusinessHours matchedBusinessHours = null;

            // 先查找特殊营业时间（business_type=2，兼容旧值1）
            // 特殊营业时间的特征：有 holidayDate 或 essentialId 或 holidayType 不为空
            for (StoreBookingBusinessHours businessHours : businessHoursList) {
                Integer businessType = businessHours.getBusinessType();
                // 判断是否为特殊营业时间：新值2，或旧值1且有关联的节假日信息
                boolean isSpecialBusinessHours = false;
                if (businessType != null) {
                    if (businessType == 2) {
                        // 新值：2 表示特殊营业时间
                        isSpecialBusinessHours = true;
                    } else if (businessType == 1) {
                        // 旧值：1 可能是特殊营业时间，需要检查是否有节假日信息
                        if (businessHours.getEssentialId() != null || 
                            businessHours.getHolidayDate() != null || 
                            (businessHours.getHolidayType() != null && !businessHours.getHolidayType().trim().isEmpty())) {
                            isSpecialBusinessHours = true;
                        }
                    }
                }
                
                if (isSpecialBusinessHours) {
                    // 特殊营业时间：需要匹配 essential_holiday_comparison 表中的日期范围
                    if (businessHours.getEssentialId() != null) {
                        EssentialHolidayComparison holiday = essentialHolidayComparisonMapper.selectById(businessHours.getEssentialId());
                        if (holiday != null && holiday.getStartTime() != null && holiday.getEndTime() != null) {
                            // 判断预约日期是否在节假日的日期范围内
                            Date startTime = holiday.getStartTime();
                            Date endTime = holiday.getEndTime();
                            if (!reservationDate.before(startTime) && !reservationDate.after(endTime)) {
                                matchedBusinessHours = businessHours;
                                break;
                            }
                        }
                    } else if (businessHours.getHolidayDate() != null) {
                        // 如果没有 essentialId，使用 holidayDate 匹配
                        if (reservationDateStr.equals(businessHours.getHolidayDate())) {
                            matchedBusinessHours = businessHours;
                            break;
                        }
                    }
                }
            }

            // 如果没有匹配到特殊营业时间，使用正常营业时间（business_type=1，兼容旧值0）
            if (matchedBusinessHours == null) {
                matchedBusinessHours = businessHoursList.stream()
                        .filter(h -> {
                            Integer businessType = h.getBusinessType();
                            // 兼容旧值：0 和新值：1 都表示正常营业时间
                            return businessType != null && (businessType == 1 || businessType == 0);
                        })
                        .findFirst()
                        .orElse(null);
            }

            // 如果 store_booking_business_hours 中没有找到匹配的营业时间，则从 store_business_info 查询
            if (matchedBusinessHours == null) {
                log.info("store_booking_business_hours 中未找到匹配的营业时间，尝试从 store_business_info 查询，storeId={}", reservation.getStoreId());
                matchedBusinessHours = findBusinessHoursFromStoreBusinessInfo(reservation, reservationDate, reservationDateStr);
            }

            if (matchedBusinessHours == null) {
                log.warn("未找到匹配的营业时间，跳过营业时间校验");
                return;
            }

            // 如果是全天营业（booking_time_type=1），不限制结束时间
            if (matchedBusinessHours.getBookingTimeType() != null && matchedBusinessHours.getBookingTimeType() == 1) {
                log.info("全天营业，不限制结束时间");
                return;
            }

            // 获取营业时间的结束时间
            String businessEndTime = matchedBusinessHours.getEndTime();
            if (businessEndTime == null || businessEndTime.trim().isEmpty()) {
                log.warn("营业时间结束时间为空，跳过校验");
                return;
            }

            // 标准化新的结束时间，提取日期部分
            String normalizedNewEndTime = normalizeDateTime(newEndTime);
            if (normalizedNewEndTime == null) {
                log.warn("新的结束时间格式错误，跳过校验，newEndTime={}", newEndTime);
                return;
            }

            // 从新的结束时间中提取日期部分（用于构建营业时间的完整日期时间）
            // normalizedNewEndTime 格式：yyyy-MM-dd HH:mm:ss
            String newEndTimeDateStr = normalizedNewEndTime.substring(0, 10); // 提取 yyyy-MM-dd

            // 将营业时间的结束时间转换为完整的日期时间格式
            // businessEndTime 是 HH:mm 格式，需要结合新结束时间的日期
            String businessEndDateTimeStr = newEndTimeDateStr + " " + businessEndTime.trim() + ":00";
            String normalizedBusinessEndTime = normalizeDateTime(businessEndDateTimeStr);
            if (normalizedBusinessEndTime == null) {
                log.warn("营业时间结束时间格式错误，跳过校验，businessEndTime={}", businessEndTime);
                return;
            }

            // 比较新的结束时间和营业时间的结束时间
            int comparison = compareTime(normalizedNewEndTime, normalizedBusinessEndTime);
            if (comparison > 0) {
                // 新的结束时间超过了营业时间的结束时间
                // 计算最多可加时的分钟数
                // 确定计算基准时间：取当前结束时间和加时开始时间的较大值
                String normalizedEndTime = normalizeDateTime(reservation.getEndTime());
                String normalizedAddTimeStart = normalizeDateTime(addTimeStart);
                String baseTime = normalizedEndTime;
                if (normalizedAddTimeStart != null && compareTime(normalizedAddTimeStart, normalizedEndTime) > 0) {
                    baseTime = normalizedAddTimeStart;
                }
                int maxAddTimeMinutes = calculateMaxAddTimeMinutes(baseTime, normalizedBusinessEndTime);
                throw new RuntimeException("最多可加时" + maxAddTimeMinutes + "分钟");
            }

            log.info("加时校验通过，新的结束时间={}，营业结束时间={}", normalizedNewEndTime, normalizedBusinessEndTime);
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw e;
            }
            log.error("校验加时是否超过营业时间失败", e);
            // 校验失败时，抛出异常以阻止加时操作
            throw new RuntimeException("校验营业时间失败：" + e.getMessage());
        }
    }

    /**
     * 从 store_business_info 表中查找匹配的营业时间
     * 
     * @param reservation 预约信息
     * @param reservationDate 预约日期
     * @param reservationDateStr 预约日期字符串（yyyy-MM-dd格式）
     * @return 匹配的营业时间对象（包装为 StoreBookingBusinessHours 格式），未找到返回 null
     */
    private StoreBookingBusinessHours findBusinessHoursFromStoreBusinessInfo(
            UserReservation reservation, Date reservationDate, String reservationDateStr) {
        try {
            // 查询门店的营业时间列表
            List<StoreBusinessInfoVo> storeBusinessInfoList = storeInfoService.getStoreInfoBusinessHours(reservation.getStoreId());
            if (storeBusinessInfoList == null || storeBusinessInfoList.isEmpty()) {
                log.warn("门店未配置 store_business_info 营业时间，storeId={}", reservation.getStoreId());
                return null;
            }

            StoreBusinessInfoVo matchedBusinessInfo = null;

            // 先查找特殊营业时间（business_type=2）
            for (StoreBusinessInfoVo businessInfo : storeBusinessInfoList) {
                if (businessInfo.getBusinessType() != null && businessInfo.getBusinessType() == 2) {
                    // 特殊营业时间：需要匹配 essential_holiday_comparison 表中的日期范围
                    if (businessInfo.getEssentialId() != null && !businessInfo.getEssentialId().trim().isEmpty()) {
                        try {
                            Integer essentialId = Integer.parseInt(businessInfo.getEssentialId().trim());
                            EssentialHolidayComparison holiday = essentialHolidayComparisonMapper.selectById(essentialId);
                            if (holiday != null && holiday.getStartTime() != null && holiday.getEndTime() != null) {
                                // 判断预约日期是否在节假日的日期范围内
                                Date startTime = holiday.getStartTime();
                                Date endTime = holiday.getEndTime();
                                if (!reservationDate.before(startTime) && !reservationDate.after(endTime)) {
                                    matchedBusinessInfo = businessInfo;
                                    break;
                                }
                            }
                        } catch (NumberFormatException e) {
                            log.warn("门店营业时间关联的节假日ID格式错误，storeId={}, essentialId={}", 
                                    reservation.getStoreId(), businessInfo.getEssentialId());
                        }
                    } else if (businessInfo.getBusinessDate() != null) {
                        // 如果没有 essentialId，使用 businessDate 匹配
                        if (reservationDateStr.equals(businessInfo.getBusinessDate())) {
                            matchedBusinessInfo = businessInfo;
                            break;
                        }
                    }
                }
            }

            // 如果没有匹配到特殊营业时间，使用正常营业时间（business_type=1）
            if (matchedBusinessInfo == null) {
                matchedBusinessInfo = storeBusinessInfoList.stream()
                        .filter(h -> h.getBusinessType() != null && h.getBusinessType() == 1)
                        .findFirst()
                        .orElse(null);
            }

            if (matchedBusinessInfo == null) {
                log.warn("store_business_info 中未找到匹配的营业时间，storeId={}", reservation.getStoreId());
                return null;
            }

            // 将 StoreBusinessInfoVo 转换为 StoreBookingBusinessHours 格式（仅用于返回 endTime）
            StoreBookingBusinessHours result = new StoreBookingBusinessHours();
            result.setEndTime(matchedBusinessInfo.getEndTime());
            result.setBookingTimeType(null); // store_business_info 没有 booking_time_type，设为 null 表示非全天
            log.info("从 store_business_info 找到匹配的营业时间，endTime={}", matchedBusinessInfo.getEndTime());
            return result;
        } catch (Exception e) {
            log.error("从 store_business_info 查询营业时间失败", e);
            return null;
        }
    }

    /**
     * 校验预约是否过期
     * 判断当前时间是否超过预约日期 + 结束时间
     */
    private boolean isReservationExpired(UserReservation reservation) {
        try {
            Date now = new Date();
            Date reservationDate = reservation.getReservationDate();
            String endTime = reservation.getEndTime();

            // 预约日期为空，视为过期
            if (reservationDate == null) {
                log.warn("预约日期为空，视为过期，reservationId={}", reservation.getId());
                return true;
            }

            // 结束时间为空，视为过期
            if (endTime == null || endTime.trim().isEmpty()) {
                log.warn("结束时间为空，视为过期，reservationId={}", reservation.getId());
                return true;
            }

            // 解析结束时间
            // endTime 可能是两种格式：
            // 1. 只有时间部分（如："09:00"）
            // 2. 完整的日期时间（如："2026-03-11 09:00"）
            String endTimeTrimmed = endTime.trim();
            Date endDateTime = null;
            
            // 尝试多种日期时间格式
            SimpleDateFormat[] dateTimeFormats = {
                    new SimpleDateFormat("yyyy-MM-dd HH:mm"),      // 完整日期时间格式
                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),   // 完整日期时间格式（带秒）
                    new SimpleDateFormat("HH:mm")                  // 只有时间格式
            };
            
            // 先尝试解析为完整日期时间格式
            boolean parsed = false;
            for (int i = 0; i < 2; i++) {
                try {
                    endDateTime = dateTimeFormats[i].parse(endTimeTrimmed);
                    parsed = true;
                    break;
                } catch (ParseException e) {
                    // 继续尝试下一个格式
                }
            }
            
            // 如果完整日期时间格式解析失败，尝试组合预约日期和时间
            if (!parsed) {
                try {
                    // 先验证是否为时间格式（HH:mm）
                    dateTimeFormats[2].parse(endTimeTrimmed);
                    // 组合预约日期和时间
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                    String dateStr = dateFormat.format(reservationDate);
                    String dateTimeStr = dateStr + " " + endTimeTrimmed;
                    endDateTime = dateTimeFormats[0].parse(dateTimeStr);
                    parsed = true;
                } catch (ParseException e) {
                    log.error("无法解析结束时间格式，reservationDate={}, endTime={}", 
                            reservationDate, endTime, e);
                    return true;
                }
            }
            
            if (endDateTime == null) {
                log.error("无法解析结束时间格式，reservationDate={}, endTime={}", 
                        reservationDate, endTime);
                return true;
            }

            // 判断当前时间是否超过结束时间
            boolean expired = now.after(endDateTime);
            if (expired) {
                log.info("预约已过期，reservationId={}, reservationDate={}, endTime={}, endDateTime={}, now={}", 
                        reservation.getId(), reservationDate, endTime, endDateTime, now);
            }
            return expired;
        } catch (Exception e) {
            log.error("校验预约是否过期失败，reservationId={}", reservation.getId(), e);
            return true;
        }
    }

    /**
     * 计算Redis过期时间（秒）：从当前时间到预订结束时间 + 3小时的秒数
     */
    private long calculateExpireSecondsWithBuffer(UserReservation reservation) {
        try {
            Date now = new Date();
            String endTime = reservation.getEndTime();

            if (endTime == null || endTime.trim().isEmpty()) {
                return 0;
            }

            SimpleDateFormat[] dateTimeFormats = {
                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),
                    new SimpleDateFormat("yyyy-MM-dd HH:mm"),
                    new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"),
                    new SimpleDateFormat("yyyy/MM/dd HH:mm")
            };

            Date endDateTime = null;
            for (SimpleDateFormat format : dateTimeFormats) {
                try {
                    endDateTime = format.parse(endTime);
                    break;
                } catch (ParseException e) {
                    // 继续尝试下一个格式
                }
            }

            if (endDateTime == null) {
                log.error("无法解析结束时间格式，endTime={}", endTime);
                return 0;
            }

            // 在结束时间基础上加3小时
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(endDateTime);
            calendar.add(Calendar.HOUR_OF_DAY, 3);
            Date expireDateTime = calendar.getTime();

            long diff = expireDateTime.getTime() - now.getTime();
            return diff > 0 ? diff / 1000 : 0;
        } catch (Exception e) {
            log.error("计算Redis过期时间失败", e);
            return 0;
        }
    }

    /**
     * 处理过期的预订核销key
     */
    private void handleExpiredReservationKey(String expiredKey) {
        try {
            String reservationNo = expiredKey.replace(RESERVATION_VERIFY_PREFIX, "");

            log.info("检测到预订核销key过期，预约号: {}, key: {}", reservationNo, expiredKey);

            LambdaQueryWrapper<UserReservation> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(UserReservation::getReservationNo, reservationNo)
                    .eq(UserReservation::getDeleteFlag, 0)
                    .last("LIMIT 1");
            UserReservation reservation = this.getOne(wrapper);

            if (reservation == null) {
                log.warn("预订核销key过期，但未找到对应的预约信息，预约号: {}", reservationNo);
                return;
            }

            log.info("开始处理预订退款，预约号: {}, 预约ID: {}", reservationNo, reservation.getId());
            refundReservation(reservation.getId());

        } catch (Exception e) {
            log.error("处理过期预订核销key失败，key: {}", expiredKey, e);
        }
    }

    /**
     * 获取订单状态文本
     */
    private String getOrderStatusText(Integer orderStatus) {
        if (orderStatus == null) {
            return "未知";
        }
        switch (orderStatus) {
            case 0: return "待支付";
            case 1: return "待使用";
            case 2: return "已完成";
            case 3: return "已过期";
            case 4: return "已取消";
            case 5: return "已关闭";
            case 6: return "退款中";
            case 7: return "已退款";
            case 8: return "商家预订";
            default: return "未知";
        }
    }

    @Override
    public shop.alien.entity.result.R<String> refundByOrder(Integer storeId, String outTradeNo, String refundAmount, 
                                                             String refundReason, Integer refundType, String payType) {
        log.info("StoreReservationServiceImpl.refundByOrder storeId={}, outTradeNo={}, refundAmount={}, refundType={}, refundReason={}, payType={}", 
                storeId, outTradeNo, refundAmount, refundType, refundReason, payType);

        try {
            // 1. 调用支付退款接口
            MerchantPaymentStrategy strategy = merchantPaymentStrategyFactory.getStrategy(payType);
            shop.alien.entity.result.R<String> refundResult = strategy.refund(storeId, outTradeNo, refundAmount, refundReason, refundType);
            
            // 2. 如果退款成功，发送通知和短信
            if (refundResult != null && shop.alien.entity.result.R.isSuccess(refundResult)) {
                log.info("支付退款成功，开始发送通知和短信，outTradeNo={}", outTradeNo);
                
                // 通过 outTradeNo 查询订单
                shop.alien.entity.store.UserReservationOrder order = userReservationOrderService.getByOutTradeNo(outTradeNo);
                if (order == null) {
                    log.warn("未找到订单信息，无法发送通知和短信，outTradeNo={}", outTradeNo);
                    return refundResult;
                }
                
                // 通过订单的 reservationId 获取预约信息
                Integer reservationId = order.getReservationId();
                if (reservationId == null) {
                    log.warn("订单未关联预约信息，无法发送通知和短信，outTradeNo={}, orderId={}", outTradeNo, order.getId());
                    return refundResult;
                }
                
                UserReservation reservation = this.getById(reservationId);
                if (reservation == null) {
                    log.warn("预约信息不存在，无法发送通知和短信，reservationId={}", reservationId);
                    return refundResult;
                }
                
                // 发送短信和通知
                sendDepositRefundSmsAndNoticeInternal(reservation, order);
                log.info("退款通知和短信发送完成，outTradeNo={}, reservationId={}", outTradeNo, reservationId);
            } else {
                log.warn("支付退款失败，不发送通知和短信，outTradeNo={}, result={}", outTradeNo, refundResult);
            }
            
            return refundResult;
        } catch (Exception e) {
            log.error("商家端主动退款异常，outTradeNo={}", outTradeNo, e);
            return shop.alien.entity.result.R.fail("退款失败：" + e.getMessage());
        }
    }

    /**
     * 获取当前登录的门店ID
     * 从JWT中获取用户ID，然后查询StoreUser获取门店ID
     *
     * @return 门店ID，如果未登录或查询失败返回null
     */
    private Integer getCurrentStoreId() {
        try {
            JSONObject userInfo = JwtUtil.getCurrentUserInfo();
            if (userInfo != null) {
                Integer userId = userInfo.getInteger("userId");
                if (userId != null) {
                    StoreUser storeUser = storeUserMapper.selectById(userId);
                    if (storeUser != null) {
                        return storeUser.getStoreId();
                    }
                }
            }
        } catch (Exception e) {
            log.warn("获取当前登录门店ID失败: {}", e.getMessage());
        }
        return null;
    }

    @Override
    public shop.alien.entity.result.R<String> refundByOrderId(Integer orderId, String refundReason, Integer refundType) {
        log.info("StoreReservationServiceImpl.refundByOrderId?orderId={}, refundReason={}, refundType={}", orderId, refundReason, refundType);
        if (orderId == null) {
            return shop.alien.entity.result.R.fail("订单ID不能为空");
        }
        UserReservationOrder order = userReservationOrderService.getById(orderId);
        if (order == null) {
            return shop.alien.entity.result.R.fail("预订订单不存在");
        }
        if (order.getPaymentStatus() == null || order.getPaymentStatus() != 1) {
            return shop.alien.entity.result.R.fail("订单未支付或已退款，无法退款");
        }
        if (order.getOrderCostType() == null || order.getOrderCostType() != 1) {
            return shop.alien.entity.result.R.fail("免费订单无需退款");
        }
        if (order.getOutTradeNo() == null || order.getOutTradeNo().trim().isEmpty()) {
            return shop.alien.entity.result.R.fail("订单无商户订单号，无法退款");
        }
        MerchantPaymentOrder paymentOrder = merchantPaymentOrderService.getPaidByOrderId(orderId);
        if (paymentOrder == null || paymentOrder.getPayType() == null || paymentOrder.getPayType().trim().isEmpty()) {
            return shop.alien.entity.result.R.fail("未找到已支付记录或支付方式，无法退款");
        }
        String amount = order.getDepositAmount() != null ? order.getDepositAmount().toString() : null;
        if (amount == null || amount.trim().isEmpty()) {
            return shop.alien.entity.result.R.fail("订单订金金额异常，无法退款");
        }
        String reason = refundReason != null && !refundReason.trim().isEmpty() ? refundReason.trim() : "按订单退款";
        int type = refundType != null ? refundType : 1;
        try {
            MerchantPaymentStrategy strategy = merchantPaymentStrategyFactory.getStrategy(paymentOrder.getPayType());
            return strategy.refund(order.getStoreId(), order.getOutTradeNo(), amount, reason, type);
        } catch (Exception e) {
            log.error("通过订单ID退款异常，orderId={}", orderId, e);
            return shop.alien.entity.result.R.fail("退款失败：" + e.getMessage());
        }
    }

    @Override
    public int retryRefundFailedOrders() {
        java.util.List<Integer> orderIds = userReservationOrderService.listOrderIdsForRefundRetry();
        if (orderIds == null || orderIds.isEmpty()) {
            return 0;
        }
        int successCount = 0;
        for (Integer orderId : orderIds) {
            try {
                shop.alien.entity.result.R<String> result = refundByOrderId(orderId, "定时重试退款", 1);
                if (result != null && shop.alien.entity.result.R.isSuccess(result)) {
                    successCount++;
                    log.info("定时重试退款成功，orderId={}", orderId);
                }
            } catch (Exception e) {
                log.error("定时重试退款异常，orderId={}", orderId, e);
            }
        }
        return successCount;
    }
}
