package com.ylx.order.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.ylx.address.domain.TAddress;
import com.ylx.address.service.TAddressService;
import com.ylx.common.core.domain.entity.SysDictData;
import com.ylx.common.core.domain.model.WxLoginUser;
import com.ylx.common.exception.ServiceException;
import com.ylx.common.utils.*;
import com.ylx.common.weixinPay.enums.WxPayTypeEnum;
import com.ylx.common.weixinPay.service.WxPayV3Service;
import com.ylx.fareSetting.service.IMaProjectFareSettingService;
import com.ylx.massage.domain.MaProject;
import com.ylx.massage.domain.MaTechnician;
import com.ylx.massage.domain.TGeoFence;
import com.ylx.massage.domain.TWxUser;
import com.ylx.massage.mapper.MaProjectMapper;
import com.ylx.massage.mapper.MaTechnicianMapper;
import com.ylx.coupon.service.CouponService;
import com.ylx.massage.service.IMaTechnicianService;
import com.ylx.massage.service.TGeoFenceService;
import com.ylx.massage.service.TWxUserService;
import com.ylx.massage.utils.OrderNumberGenerator;
import com.ylx.message.enums.TriggerEventEnum;
import com.ylx.message.service.IMessageService;
import com.ylx.order.domain.AfterSalesService;
import com.ylx.order.domain.OrderStatusFlow;
import com.ylx.order.domain.TOrder;
import com.ylx.order.domain.dto.*;
import com.ylx.order.domain.vo.*;
import com.ylx.order.domain.vo.merchant.MerchantCancelOrderDTO;
import com.ylx.order.domain.vo.merchant.MerchantOrderDetailVO;
import com.ylx.order.domain.vo.merchant.OrderCustomerPhoneVO;
import com.ylx.order.domain.vo.merchant.OrderPageVO;
import com.ylx.order.enums.*;
import com.ylx.order.mapper.TOrderMapper;
import com.ylx.order.service.*;
import com.ylx.project.domain.Project;
import com.ylx.project.service.ProjectService;
import com.ylx.shopingfundsdetail.domain.vo.ShoppingFundsDetailAddDto;
import com.ylx.shopingfundsdetail.enums.ShoppingFundsExpenseTypeEnum;
import com.ylx.shopingfundsdetail.service.ShoppingFundsDetailService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static com.ylx.order.constant.OrderConstant.*;


/**
 * 订单表 服务实现类
 */
@Service
@Slf4j
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements TOrderService {
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M月d日");
    private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");

    @Resource
    private ProjectService projectService;
    @Resource
    private TAddressService addressService;
    @Resource
    private IMaTechnicianService maTechnicianService;
    @Resource
    private OrderNumberGenerator orderNumberGenerator;
    @Resource
    private TWxUserService wxUserService;
    @Resource
    private ShoppingFundsDetailService shoppingFundsDetailService;
    @Resource
    private CouponService couponService;
    @Resource
    private WxPayV3Service wxPayV3Service;

    @Resource
    private OrderStatusFlowService orderStatusFlowService;

    @Resource
    private MaTechnicianMapper maTechnicianMapper;

    @Resource
    private MaProjectMapper maProjectMapper;
    @Resource
    private IAfterSalesServiceService afterSalesServiceService;
    @Resource
    private IMessageService messageService;

    @Resource
    private TGeoFenceService geoFenceService;

    @Value("${ylx.sendSmsEnabled:false}")
    private boolean sendSmsEnabled;

    @Value("${ylx.fixedVerifyCode:123456}")
    private String fixedVerifyCode;

    @Resource
    private IMaProjectFareSettingService maProjectFareSettingService;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> submitOrder(OrderSubmitDTO dto) {
        Map<String, Object> map = new HashMap<>();

        // 1. 获取并校验当前用户
        WxLoginUser wxLoginUser = getCurrentWxLoginUser();
        Long userId = Long.parseLong(wxLoginUser.getId());
        String openId = wxLoginUser.getCOpenid();

        // 2. 获取并校验项目信息
        Project project = this.projectService.getById(dto.getProjectId());
        if (ObjectUtil.isNull(project)) {
            throw new ServiceException("项目不存在");
        }

        // 3. 获取并校验商户信息
        MaTechnician maTechnician = this.maTechnicianService.getById(dto.getMerchantId());
        if (ObjectUtil.isNull(maTechnician)) {
            throw new ServiceException("商户不存在");
        }

        // 4. 获取联系人信息
        TAddress address = this.addressService.getById(dto.getAddressId());
        if (ObjectUtil.isNull(address)) {
            throw new ServiceException("客户联系地址不存在");
        }

        // 5. 获取商户地址信息
        List<TAddress> merchantAddressList = this.addressService.list(new LambdaQueryWrapper<TAddress>().eq(TAddress::getMerchantId, dto.getMerchantId()).eq(TAddress::getUserType, 2).eq(TAddress::getIsDelete, 0));
        if (CollUtil.isEmpty(merchantAddressList)) {
            throw new ServiceException("商户地址不存在");
        }

        // 6. 计算价格与优惠券核销 (核心优化：先算钱、扣券，再落库)
        BigDecimal basePrice = Optional.ofNullable(project.getPrice()).orElse(BigDecimal.ZERO);
        BigDecimal trafficFee = Optional.ofNullable(dto.getTrafficFee()).orElse(BigDecimal.ZERO);
        BigDecimal couponDiscount = BigDecimal.ZERO;

        if (ObjectUtil.isNotNull(dto.getCouponId())) {
            // 计算抵扣金额
            couponDiscount = this.couponService.calculateDiscountAmount(dto.getCouponId(), openId, basePrice);
            // 立即核销优惠券，保证与订单在同一事务中
            this.couponService.useCoupon(dto.getCouponId(), openId, null, 1);
        }

        // 实付金额 = 商品原价 - 优惠券优惠 + 车费
        BigDecimal finalAmount = basePrice.subtract(couponDiscount).add(trafficFee).setScale(2, RoundingMode.HALF_UP);

        // 7. 组装订单对象
        TOrder order = buildOrder(dto, project, maTechnician, address, merchantAddressList, basePrice, trafficFee, couponDiscount, finalAmount, userId);

        // 8. 计算商户、平台分佣金额
        this.calculateOrderIncome(order, finalAmount, project.getMerchantShareRatio());

        // 9. 保存订单
        boolean saveResult = this.save(order);
        if (!saveResult) {
            throw new ServiceException("添加订单失败");
        }

        // 10. 更新优惠券关联的真实订单ID (因为刚才核销时订单还没生成，这里补上)
        if (ObjectUtil.isNotNull(dto.getCouponId())) {
            this.couponService.useCoupon(dto.getCouponId(), openId, order.getId(), 1);
        }

        // 发送消息
        Map<String, Object> variables = new HashMap<>();
        variables.put("amount", order.getFinalAmount());
        variables.put("orderId", order.getId());

        // 11. 处理余额支付逻辑
        if (ObjectUtil.equals(PaymentMethodEnum.BALANCE.getCode(), dto.getPaymentMethod())) {
            this.messageService.sendMessage(userId, TriggerEventEnum.ORDER_PAY_SUCCESS.getDesc(), variables);
            handleBalancePayment(userId, finalAmount, order);
            map.put("orderId", order.getId());
            return map;
        } else {
            this.messageService.sendMessage(userId, TriggerEventEnum.ORDER_PENDING_PAYMENT.getDesc(), variables);
            return createWxPayOrder(order, wxLoginUser);
        }
    }

    /**
     * 用户端订单列表
     *
     * @param dto
     * @return
     */
    @Override
    public Page<OrderDateQueryVo> queryOrderList(OrderDateQueryDTO dto) {
        // 1. 构造分页对象
        Page<TOrder> page = new Page<>(dto.getCurrent(), dto.getSize());

        // 2. 构造查询条件
        LambdaQueryWrapper<TOrder> wrapper = new LambdaQueryWrapper<>();
        if (dto.getStartDate() != null) {
            LocalDateTime start = dto.getStartDate().atStartOfDay();
            wrapper.ge(TOrder::getCreateTime, start);
        }
        if (dto.getEndDate() != null) {
            LocalDateTime end = dto.getEndDate().atTime(LocalTime.MAX);
            wrapper.le(TOrder::getCreateTime, end);
        }
        if (StrUtil.isNotBlank(dto.getProjectName())) {
            wrapper.like(TOrder::getProjectName, dto.getProjectName());
        }
        if (StrUtil.isNotBlank(dto.getMerchantNickName())) {
            wrapper.like(TOrder::getMerchantNickName, dto.getMerchantNickName());
        }
        wrapper.eq(TOrder::getIsDelete, 0);
        wrapper.orderByDesc(TOrder::getCreateTime);

        // 3. 执行分页查询
        Page<TOrder> orderPage = baseMapper.selectPage(page, wrapper);

        // 4. 转换 VO
        List<OrderDateQueryVo> voList = orderPage.getRecords().stream().map(this::convertToVo).collect(Collectors.toList());


        // 5. 返回分页结果

        Page<OrderDateQueryVo> voPage = new Page<>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal());
        voPage.setRecords(voList);

        return voPage;
    }

    /**
     * 用户端逻辑删除
     *
     * @param orderId
     */
    @Override
    public void logicDeleteOrder(Long orderId) {
        // 1. 查询订单是否存在
        TOrder order = getById(orderId);
        if (order == null) {
            throw new ServiceException("订单不存在");
        }

        // 3. 如果已经是删除状态，可提示或直接返回
        if (Integer.valueOf(1).equals(order.getIsDelete())) {
            throw new ServiceException("订单已在回收站中");
        }

        // 4. 执行逻辑删除
        order.setIsDelete(DELETED);
        order.setDeletedTime(LocalDateTime.now());
        boolean updated = updateById(order);
        if (!updated) {
            throw new ServiceException("删除失败，请稍后重试");
        }
        // 5. 事务成功，返回（Controller中会返回 success 信息）
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processOrderPayment(WxPayOrderNotifyV3Result.DecryptNotifyResult result, TWxUser wxUser, TOrder order) {
        // 更新订单状态
        this.lambdaUpdate().set(TOrder::getStatus, OrderStatusEnum.PENDING_DISPATCH.getCode()).eq(TOrder::getId, order.getId()).update();

        // 插入订单流转记录
        OrderUpdateStatusDTO dto = new OrderUpdateStatusDTO();
        dto.setOrderId(order.getId());
        dto.setStatus(OrderStatusEnum.PENDING_DISPATCH.getCode());
        this.updateOrderStatus(dto);

        // 发送消息
        Map<String, Object> variables = new HashMap<>();
        variables.put("amount", order.getFinalAmount());
        variables.put("orderId", order.getId());
        this.messageService.sendMessage(Long.parseLong(wxUser.getId()), TriggerEventEnum.ORDER_PAY_SUCCESS.getDesc(), variables);
    }

    /**
     *
     * 更新订单状态（修改订单表状态必须使用此接口）
     *
     * @param dto
     */
    @Override
    public void updateOrderStatus(OrderUpdateStatusDTO dto) {
        Long orderId = dto.getOrderId();
        Integer newStatus = dto.getStatus();

        // 1. 查询订单
        TOrder order = this.baseMapper.selectById(orderId);
        if (order == null) {
            throw new ServiceException("订单不存在");
        }
        Integer oldStatus = order.getStatus();

        // 2. 更新订单状态
        TOrder updateOrder = new TOrder();
        updateOrder.setId(orderId);
        updateOrder.setStatus(newStatus);
        // 可根据状态同步更新对应时间字段（例如：支付完成时间、接单时间等），此处省略具体业务逻辑
        int updateCount = this.baseMapper.updateById(updateOrder);
        if (updateCount == 0) {
            throw new ServiceException("更新订单状态失败");
        }

        // 3. 记录状态流转流水
        OrderStatusFlow flow = new OrderStatusFlow();
        flow.setOrderId(orderId);
        flow.setStatus(newStatus);
        orderStatusFlowService.getBaseMapper().insert(flow);

        log.info("订单状态变更: orderId={}, oldStatus={}, newStatus={}", orderId, oldStatus, newStatus);
    }


    /**
     * 将 TOrder 转换为 OrderDateQueryVo，并处理 serviceTime 字段
     */
    private OrderDateQueryVo convertToVo(TOrder order) {
        OrderDateQueryVo vo = new OrderDateQueryVo();
        // 拷贝相同字段
        BeanUtils.copyProperties(order, vo);
        // 手动设置字段名不一致或需要特殊处理的
        vo.setOrderStatus(order.getStatus());
        vo.setOrderStatusName(getOrderStatusName(order.getStatus()));
        // 服务时间范围：预约开始时间 + 项目时长（分钟）
        // 使用 startTime 和 completedTime 构建服务时间展示
        String serviceTime = buildServiceTime(order.getStartTime(), order.getCompletedTime());
        vo.setServiceTime(serviceTime);

        // 获取订单售后数据
        this.fillCurrentAfterSaleInfo(vo, order.getId());

        return vo;
    }

    /**
     * 订单状态码 -> 中文描述
     */
    private String getOrderStatusName(Integer status) {
        return OrderStatusEnum.getInfoByCode(status);
    }

    /**
     * 拼接服务时间字符串，例如 “2026-06-08 14:30 (90分钟)”
     */
    private String buildServiceTime(LocalDateTime startTime, LocalDateTime completedTime) {
        if (startTime == null || completedTime == null) {
            return "";
        }
        String date = startTime.format(dateFormatter);
        String start = startTime.format(timeFormatter);
        String end = completedTime.format(timeFormatter);
        return date + " " + start + "-" + end;
    }

    /**
     * 组装订单对象
     */
    private TOrder buildOrder(OrderSubmitDTO dto, Project project, MaTechnician maTechnician, TAddress address, List<TAddress> merchantAddressList, BigDecimal basePrice, BigDecimal trafficFee, BigDecimal couponDiscount, BigDecimal finalAmount, Long userId) {
        TOrder order = new TOrder();
        order.setOrderNo(orderNumberGenerator.generateNextOrderNumber(""));
        order.setUserId(userId);
        order.setProjectId(dto.getProjectId());
        order.setProjectType(project.getType());
        order.setProjectName(project.getTitle());
        order.setProjectCover(project.getCover());
        order.setHighlight(project.getHighlight());
        order.setAppointmentStartTime(dto.getAppointmentStartTime());

        // 根据项目的unitType字段将小时转换为分钟
        Integer projectDuration = this.convertToMinutes(project.getStandardDuration(), project.getUnitType());
        order.setProjectDuration(projectDuration);
        order.setMerchantId(dto.getMerchantId());
        order.setMerchantType(maTechnician.getTechType());
        order.setMerchantNickName(maTechnician.getTeNickName());
        order.setMerchantAvatar(maTechnician.getTeAvatar());
        order.setContactPersonName(address.getUserName());
        order.setContactPhoneNumber(address.getPhone());
        order.setContactAddressInfo(address.getDetailAddress());
        order.setCityCode(address.getCityCode());
        order.setBasePrice(basePrice);
        order.setCouponDiscount(couponDiscount);
        order.setTrafficFee(trafficFee);
        order.setFinalAmount(finalAmount);
        order.setCouponId(dto.getCouponId());
        order.setCreateTime(DateUtils.getNowDate());
        order.setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode());
        order.setExecStatus(0);
        order.setDispatchedStatus(DispatchedStatusEnum.UN_DISPATCHED.getCode());
        order.setPaymentMethod(dto.getPaymentMethod());

        // 经纬度安全赋值
        order.setUserLatitude(address.getLatitude());
        order.setUserLongitude(address.getLongitude());

        merchantAddressList.stream().filter(a -> a.getType() == 1).findFirst().ifPresent(addr -> {
            order.setMerchantLongitude(addr.getLongitude());
            order.setMerchantLatitude(addr.getLatitude());
        });
        merchantAddressList.stream().filter(a -> a.getType() == 2).findFirst().ifPresent(addr -> {
            order.setVirtualLongitude(addr.getLongitude());
            order.setVirtualLatitude(addr.getLatitude());
        });

        // 设置支付状态
        if (ObjectUtil.equals(PaymentMethodEnum.BALANCE.getCode(), dto.getPaymentMethod())) {
            order.setStatus(OrderStatusEnum.PENDING_DISPATCH.getCode());
            order.setPaidTime(LocalDateTime.now());
        } else {
            order.setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode());
        }
        return order;
    }

    /**
     * 处理余额支付 (核心优化：数据库原子操作防并发)
     */
    private void handleBalancePayment(Long userId, BigDecimal finalAmount, TOrder order) {
        // 1. 数据库层面原子扣减余额，并校验余额是否充足
        boolean deductSuccess = this.wxUserService.lambdaUpdate().setSql("d_balance = d_balance - " + finalAmount).eq(TWxUser::getId, userId)
                // 防止余额超扣的核心：当前余额必须大于等于扣减金额
                .apply("d_balance >= {0}", finalAmount).update();

        if (!deductSuccess) {
            throw new ServiceException("余额不足，扣款失败");
        }

        // 2. 重新查询最新余额用于记录流水
        TWxUser user = this.wxUserService.getById(userId);

        // 3. 记录购物金明细
        ShoppingFundsDetailAddDto detailDto = new ShoppingFundsDetailAddDto();
        detailDto.setUserId(userId.toString());
        detailDto.setAmount(finalAmount);
        detailDto.setOrderNo(order.getOrderNo());
        detailDto.setExpenseType(ShoppingFundsExpenseTypeEnum.CONSUMPTION.getCode());
        detailDto.setBalance(user.getdBalance());
        this.shoppingFundsDetailService.addShoppingFundsDetail(detailDto);

        // 4. 记录状态流转流水
        OrderUpdateStatusDTO dto = new OrderUpdateStatusDTO();
        dto.setOrderId(order.getId());
        dto.setStatus(order.getStatus());
        this.updateOrderStatus(dto);
    }

    /**
     * 创建微信支付订单（事务外执行，减少事务时长）
     */
    private Map<String, Object> createWxPayOrder(TOrder order, WxLoginUser wxLoginUser) {
        try {
            return wxPayV3Service.createV3JsapiOrder(order.getOrderNo(), order.getFinalAmount(), "购买情绪价值商品", wxLoginUser.getCOpenid(), WxPayTypeEnum.EMOTION_GOODS.getCode());
        } catch (Exception e) {
            log.error("微信支付下单失败，订单号: {}", order.getOrderNo(), e);
            throw new ServiceException("支付服务异常，请稍后重试");
        }
    }

    /**
     * 根据订单ID查询订单详情
     */
    @Override
    public OrderDetailVO getOrderDetailById(Long orderId) {
        if (orderId == null || orderId <= 0) {
            throw new ServiceException("订单ID无效");
        }

        // 1. 根据ID查询订单主表
        TOrder order = this.baseMapper.selectById(orderId);
        if (order == null || order.getIsDelete() == DELETED) {
            throw new ServiceException("订单不存在，订单ID：" + orderId);
        }

        // 2. 查询订单状态流转记录
        LambdaQueryWrapper<OrderStatusFlow> flowWrapper = Wrappers.<OrderStatusFlow>lambdaQuery().eq(OrderStatusFlow::getOrderId, order.getId()).orderByAsc(OrderStatusFlow::getCreateTime);
        List<OrderStatusFlow> flowList = orderStatusFlowService.getBaseMapper().selectList(flowWrapper);

        // 3. 组装VO
        return buildOrderDetailVO(order, flowList);
    }

    /**
     * 将订单实体和流转记录转换为详情VO
     */
    private OrderDetailVO buildOrderDetailVO(TOrder order, List<OrderStatusFlow> flowList) {
        OrderDetailVO vo = new OrderDetailVO();

        // 基础订单信息
        vo.setOrderStatus(order.getStatus());
        vo.setOrderStatusName(OrderStatusEnum.getInfoByCode(order.getStatus()));
        vo.setMerchantAvatar(order.getMerchantAvatar());
        // 1. 获取服务次数（从项目表获取 projectUsersNum）
        Integer serviceNum = null;
        if (order.getProjectId() != null && order.getMerchantId() != null) {
            MaProject project = maProjectMapper.selectByProjectIdAndMerchantId(order.getProjectId(), order.getMerchantId());
            if (project != null && project.getProjectUsersNum() != null) {
                serviceNum = project.getProjectUsersNum().intValue();
            }
        }
        vo.setServiceNum(serviceNum);

        // 查询技师表
        if (order.getMerchantId() != null) {
            // merchantId 为 Long，技师表 id 为 Integer，需转换
            MaTechnician technician = maTechnicianMapper.selectById(order.getMerchantId().intValue());
            if (technician != null) {
                //评分
                vo.setStar(technician.getNStar());
                //简介
                vo.setBrief(technician.getTeBrief());
            } else {
                log.warn("技师信息不存在，merchantId: {}", order.getMerchantId());
            }
        } else {
            // 无 merchantId，字段置空（或可后续扩展）
            vo.setStar(null);
            vo.setBrief(null);
        }

        vo.setProjectName(order.getProjectName());
        vo.setProjectDuration(order.getProjectDuration());
        vo.setHighlight(order.getHighlight());
        vo.setFinalAmount(order.getFinalAmount());
        vo.setBasePrice(order.getBasePrice());
        vo.setTrafficFee(order.getTrafficFee());
        vo.setCouponDiscount(order.getCouponDiscount());
        vo.setOrderNo(order.getOrderNo());
        vo.setOrderId(String.valueOf(order.getId()));
        vo.setMerchantName(order.getMerchantNickName());
        vo.setMerchantId(order.getMerchantId());
        // 时间处理
        if (order.getAppointmentStartTime() != null) {
            LocalDateTime start = order.getAppointmentStartTime();
            // 预约结束时间 = 开始时间 + 项目时长（分钟）
            LocalDateTime end = start.plusMinutes(order.getProjectDuration() == null ? 0 : order.getProjectDuration());

            // 原始格式
            vo.setAppointmentStartTime(start.format(DATE_TIME_FORMATTER));
            vo.setAppointmentEndTime(end.format(DATE_TIME_FORMATTER));

            // 生成“5月12日 16:00-18:00”格式
            String timeRange = formatAppointmentTimeRange(start, end);
            vo.setAppointmentTimeRange(timeRange);
        }
        if (order.getCreateTime() != null) {
            vo.setCreateTime(DATE_FORMAT.format(order.getCreateTime()));
        }
        if (order.getPaidTime() != null) {
            vo.setPaidTime(order.getPaidTime().format(DATE_TIME_FORMATTER));
        }

        vo.setContactAddresInfo(order.getContactAddressInfo());
        // 联系信息组合：姓名 + 手机号（中间4位脱敏）
        String contactInfo = buildContactInfo(order.getContactPersonName(), order.getContactPhoneNumber());
        vo.setContactInfo(contactInfo);

        vo.setPaymentMethod(order.getPaymentMethod());
        vo.setPaymentMethodName(PaymentMethodEnum.getNameByCode(order.getPaymentMethod()));

        // 获取订单售后数据
        this.fillCurrentAfterSaleInfo(vo, order.getId());

        // 订单状态流转列表转换
        List<OrderStatusFlowVO> flowVOList = flowList.stream().map(this::convertToFlowVO).sorted(Comparator.comparing(OrderStatusFlowVO::getCreateTime)).collect(Collectors.toList());
        vo.setOrderStatusFlowList(flowVOList);

        return vo;
    }

    /**
     * 格式化预约时间范围
     *
     * @param start 预约开始时间
     * @param end   预约结束时间
     * @return 示例：5月12日 16:00-18:00
     */
    private String formatAppointmentTimeRange(LocalDateTime start, LocalDateTime end) {
        if (start == null) {
            return "";
        }
        // 日期格式：M月d日（不带年份，同一年内）
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M月d日");
        String dateStr = start.format(dateFormatter);
        // 时间格式：HH:mm
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
        String startTime = start.format(timeFormatter);
        String endTime = (end != null) ? end.format(timeFormatter) : "";
        return dateStr + " " + startTime + "-" + endTime;
    }

    /**
     * 构建联系信息（姓名 + 脱敏手机号）
     * 示例：王先生，188****5555
     */
    private String buildContactInfo(String name, String phone) {
        if (!StringUtils.hasText(name) && !StringUtils.hasText(phone)) {
            return "";
        }
        String maskedPhone = "";
        if (StringUtils.hasText(phone) && phone.length() >= 11) {
            maskedPhone = phone.substring(0, 3) + "****" + phone.substring(7);
        } else if (StringUtils.hasText(phone)) {
            maskedPhone = phone; // 长度不足则直接显示
        }
        if (StringUtils.hasText(name)) {
            return name + "，" + maskedPhone;
        } else {
            return maskedPhone;
        }
    }

    /**
     * 将 OrderStatusFlow 转换为 OrderStatusFlowVO
     */
    private OrderStatusFlowVO convertToFlowVO(OrderStatusFlow flow) {
        OrderStatusFlowVO vo = new OrderStatusFlowVO();
        vo.setStatusName(OrderStatusEnum.getInfoByCode(flow.getStatus()));
        if (flow.getCreateTime() != null) {
            vo.setCreateTime(DATE_FORMAT.format(flow.getCreateTime()));
        }
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int cancelOrder(OrderCancleDTO dto) {
        // 1. 查询订单
        TOrder order = this.baseMapper.selectById(dto.getId());
        if (order == null) {
            throw new IllegalArgumentException("订单不存在");
        }

        // 2. 权限校验：仅限订单所属用户取消
        Long currentUserId = SecurityUtils.getUserId();
        if (!order.getUserId().equals(currentUserId)) {
            throw new RuntimeException("您无权操作此订单");
        }

        // 3. 状态校验：只有待付款(0)可以取消
        if (!OrderStatusEnum.PENDING_PAYMENT.getCode().equals(order.getStatus())) {
            throw new RuntimeException("只有待付款订单可以取消，当前状态无法取消");
        }

        // 4. 重复取消校验（可选）
        if (OrderStatusEnum.CANCELLED.getCode().equals(order.getStatus())) {
            throw new RuntimeException("订单已取消，请勿重复操作");
        }

        // 5. 执行取消更新
        TOrder updateOrder = new TOrder();
        updateOrder.setId(dto.getId());
        updateOrder.setStatus(OrderStatusEnum.CANCELLED.getCode());
        updateOrder.setCancelledReason(dto.getCancelledReason());
        updateOrder.setCancelledTime(LocalDateTime.now());
        updateOrder.setUpdateTime(new Date());

        int rows = this.baseMapper.updateById(updateOrder);
        if (rows <= 0) {
            throw new RuntimeException("取消失败，请稍后重试");
        }

        // 6. 待付款订单取消无需退款（因为未支付），但如需记录日志等可留扩展
        log.info("订单取消成功，订单ID：{}，原因：{}", dto.getId(), dto.getCancelledReason());

        return rows;
    }

    @Override
    public Boolean bookingCheck(BookingCheckDTO dto) {

        // 1. 根据 projectId 获取项目时长
        Project project = projectService.getById(dto.getProjectId());
        if (ObjectUtil.isNull(project)) {
            throw new ServiceException("项目不存在");
        }
        Integer duration = convertToMinutes(project.getStandardDuration(), project.getUnitType());

        // 2. 组装目标时间段
        LocalDateTime newStart = LocalDateTime.of(dto.getAppointmentDate(), dto.getStartTime());

        // 3. 查询该商户当天的【有效占用】订单
        LocalDateTime dayStart = dto.getAppointmentDate().atStartOfDay();
        LocalDateTime dayEnd = dto.getAppointmentDate().atTime(LocalTime.MAX);

        List<TOrder> validOrders = this.baseMapper.selectValidOrdersByMerchantAndDate(dto.getMerchantId(), dto.getProjectId(), dayStart, dayEnd);

        return isAppointmentSlotAvailable(newStart, duration, validOrders);
    }

    @Override
    public Page<OrderPageVO> queryMerchantOrderList(OrderDateQueryDTO dto) {
        // 1. 构造分页对象
        Page<OrderPageVO> page = new Page<>(dto.getCurrent(), dto.getSize());

        // 2.获取当前登录用户
        WxLoginUser wxLoginUser = getCurrentWxLoginUser();
        Long merchantId = Long.parseLong(wxLoginUser.getId());
        dto.setMerchantId(merchantId);

        // 3. 查询商户默认地址
        TAddress merchantAddress = getMerchantDefaultAddress(merchantId);
        // 填充商户坐标到查询DTO，用于SQL距离筛选（如有）
        fillMerchantLonLatToDto(dto, merchantAddress);

        // 4. Mapper分页查询订单基础数据
        page = baseMapper.queryMerchantOrderList(page, dto);
        List<OrderPageVO> voRecords = page.getRecords();
        // 无订单直接返回，跳过围栏、标签计算逻辑
        if (CollUtil.isEmpty(voRecords)) {
            return page;
        }

        // 5. 查询当前城市有效地理围栏，提前过滤非法围栏
        List<TGeoFence> validFenceList = geoFenceService.selectValidFences(dto.getCityCode());
        List<TGeoFence> usableFence = filterUsableFence(validFenceList);


        // 6. 批量遍历订单，计算风险区域+客户标签
        LocalDateTime now = DateUtils.getNowLocalDateTime();
        for (OrderPageVO vo : voRecords) {
            // 计算高风险围栏信息
            calcOrderGeoFenceInfo(vo, usableFence);
            // 计算客户标签
            Integer tagCode = calcCustomerTagCode(vo, merchantId, now);
            vo.setCustomerTagCode(tagCode);
            vo.setCustomerTagDesc(CustomerTagEnum.getDescByCode(tagCode));
            vo.setAppointmentTimeRange(formatTimeRange(vo.getAppointmentStartTime(), vo.getProjectDuration()));

            this.fillCurrentAfterSaleInfo(vo, vo.getId());
        }

        return page;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectOrder(OrderRejectDTO dto) {

        // 1. 获取并校验当前用户
        WxLoginUser wxLoginUser = getCurrentWxLoginUser();
        Long userId = Long.parseLong(wxLoginUser.getId());

        // 2. 校验订单状态
        Long orderId = dto.getId();
        TOrder order = validateAndGetOrder(dto.getId(), userId);

        // 判断当前订单状态是否为【待接单】，非待接单直接抛出异常（前端toast提示操作失败）
        if (!ObjectUtil.equals(order.getStatus(), OrderStatusEnum.PENDING_ACCEPT.getCode())) {
            throw new ServiceException("操作失败，请重试");
        }

        if (ObjectUtil.equals(order.getStatus(), OrderStatusEnum.REJECTED.getCode())) {
            throw new ServiceException("无需重复拒绝接单");
        }

        // 3. 修改订单状态
        TOrder updateOrder = new TOrder();
        updateOrder.setId(orderId);
        updateOrder.setStatus(OrderStatusEnum.REJECTED.getCode());
        updateOrder.setRejectedTime(DateUtils.getNowLocalDateTime());
        updateOrder.setRejectedReason(dto.getRejectReason());
        updateOrder.setUpdateBy(wxLoginUser.getCNickName());
        updateOrder.setUpdateTime(DateUtils.getNowDate());
        this.baseMapper.updateById(updateOrder);

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void acceptOrder(OrderAcceptDTO dto) {

        // 1. 获取并校验当前用户
        WxLoginUser wxLoginUser = getCurrentWxLoginUser();
        Long userId = Long.parseLong(wxLoginUser.getId());

        // 2. 校验订单状态
        Long orderId = dto.getId();
        TOrder order = validateAndGetOrder(dto.getId(), userId);

        if (ObjectUtil.equals(order.getStatus(), OrderStatusEnum.PENDING_SERVICE.getCode())) {
            throw new ServiceException("无需重复接单");
        }

        // 3. 修改订单状态
        TOrder updateOrder = new TOrder();
        updateOrder.setId(orderId);
        updateOrder.setStatus(OrderStatusEnum.PENDING_SERVICE.getCode());
        updateOrder.setDispatchedStatus(DispatchedStatusEnum.DISPATCHED.getCode());
        updateOrder.setConfirmedTime(DateUtils.getNowLocalDateTime());
        updateOrder.setUpdateBy(wxLoginUser.getCNickName());
        updateOrder.setUpdateTime(DateUtils.getNowDate());
        this.baseMapper.updateById(updateOrder);

    }

    @Override
    public OrderCustomerPhoneVO getCustomerPhoneByOrderId(OrderPhoneQueryDTO dto) {
        // 1. 获取当前登录商户
        WxLoginUser wxLoginUser = getCurrentWxLoginUser();
        Long merchantId = Long.parseLong(wxLoginUser.getId());
        Long orderId = dto.getOrderId();

        // 2. 查询订单：只查归属商户、未删除、手机号字段
        TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class)
                .select(TOrder::getId, TOrder::getMerchantId, TOrder::getContactPhoneNumber)
                .eq(TOrder::getId, orderId)
                .eq(TOrder::getMerchantId, merchantId)
                .eq(TOrder::getIsDelete, NOT_DELETE));

        // 3. 校验订单
        if (ObjectUtil.isNull(order)) {
            throw new ServiceException("订单不存在或不属于当前商户");
        }
        String phone = order.getContactPhoneNumber();
        if (StrUtil.isBlank(phone)) {
            throw new ServiceException("该订单未预留客户联系电话");
        }

        // 4. 封装返回明文手机号
        OrderCustomerPhoneVO vo = new OrderCustomerPhoneVO();
        vo.setContactPhoneNumber(phone);
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void merchantCancelOrderToAfterSale(MerchantCancelOrderDTO dto) {
        // 1. 获取当前登录商户
        WxLoginUser loginUser = getCurrentWxLoginUser();
        Long merchantId = Long.parseLong(loginUser.getId());
        Long orderId = dto.getOrderId();
        String cancelReason = dto.getCancelReason();

        // 2. 查询订单基础信息
        TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class)
                .select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus, TOrder::getUserId, TOrder::getFinalAmount, TOrder::getProjectId)
                .eq(TOrder::getId, orderId)
                .eq(TOrder::getMerchantId, merchantId)
                .eq(TOrder::getIsDelete, NOT_DELETE));

        // 订单不存在/不属于当前商户
        if (ObjectUtil.isNull(order)) {
            throw new ServiceException("订单不存在，无法操作");
        }

        // 3. 状态校验：仅【商户已接单】允许取消转售后
        Integer statusCode = order.getStatus();
        OrderStatusEnum orderStatus = OrderStatusEnum.fromCode(statusCode);
        if (orderStatus == null || (!OrderStatusEnum.PENDING_SERVICE.getCode().equals(statusCode) && !OrderStatusEnum.IN_SERVICE.getCode().equals(statusCode))) {
            String currentText = OrderStatusEnum.getInfoByCode(statusCode);
            throw new ServiceException("仅【待服务、服务中】订单可操作，当前订单状态：" + currentText);
        }

        // 4. 更新订单为售后状态，并发乐观锁校验
        if (OrderStatusEnum.PENDING_SERVICE.getCode().equals(statusCode)) {
            // ========== 分支1：待服务 → 退回待派单，无售后、无退款 ==========
            LambdaUpdateWrapper<TOrder> updateWrapper = Wrappers.lambdaUpdate(TOrder.class)
                    .set(TOrder::getStatus, OrderStatusEnum.PENDING_DISPATCH.getCode())
                    .set(TOrder::getCancelledTime, LocalDateTime.now())
                    .set(TOrder::getCancelledReason, cancelReason)
                    .set(TOrder::getUpdateTime, LocalDateTime.now())
                    .set(TOrder::getUpdateBy, loginUser.getCNickName())
                    .set(TOrder::getDispatchedStatus, DispatchedStatusEnum.UN_DISPATCHED.getCode())
                    .eq(TOrder::getId, orderId)
                    .eq(TOrder::getStatus, statusCode);
            int row = baseMapper.update(null, updateWrapper);
            if (row <= 0) {
                throw new ServiceException("订单状态已变更，请刷新重试");
            }
        } else if (OrderStatusEnum.IN_SERVICE.getCode().equals(statusCode)) {
            // ========== 分支2：服务中 → 转售后、生成售后单、发起退款 ==========
            // 1. 更新订单信息
            LambdaUpdateWrapper<TOrder> updateWrapper = Wrappers.lambdaUpdate(TOrder.class)
                    .set(TOrder::getCancelledTime, LocalDateTime.now())
                    .set(TOrder::getCancelledReason, cancelReason)
                    .set(TOrder::getUpdateTime, LocalDateTime.now())
                    .set(TOrder::getUpdateBy, loginUser.getCNickName())
                    .eq(TOrder::getId, orderId)
                    .eq(TOrder::getStatus, statusCode);
            int row = baseMapper.update(null, updateWrapper);
            if (row <= 0) {
                throw new ServiceException("订单状态已变更，请刷新重试");
            }

            // 2. 创建售后单
            AfterSalesServiceDTO afterSalesServiceDTO = new AfterSalesServiceDTO();
            afterSalesServiceDTO.setOrderId(orderId);
            afterSalesServiceDTO.setReason(cancelReason);
            this.afterSalesServiceService.applyAfterSale(afterSalesServiceDTO);
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void startService(MerchantOrderOperateDTO dto) {

        // 1. 获取当前登录商户
        WxLoginUser loginUser = getCurrentWxLoginUser();
        Long merchantId = Long.parseLong(loginUser.getId());
        Long orderId = dto.getOrderId();

        TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class)
                .select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus, TOrder::getArrivedTime)
                .eq(TOrder::getId, orderId)
                .eq(TOrder::getMerchantId, merchantId)
                .eq(TOrder::getIsDelete, NOT_DELETE));
        if (ObjectUtil.isNull(order)) {
            throw new ServiceException("订单不存在或不属于当前商户");
        }

        // 判断当前订单状态判断是否为【已到达】的订单，非【已到达】订单直接抛出异常（前端toast提示操作失败）
        if (ObjectUtil.isNull(order.getArrivedTime())) {
            throw new ServiceException("操作失败，请重试");
        }

        Integer status = order.getStatus();
        if (!OrderStatusEnum.PENDING_SERVICE.getCode().equals(status)) {
            throw new ServiceException("仅【待服务】订单可开始服务，当前状态：" + OrderStatusEnum.getInfoByCode(status));
        }

        // 更新状态+服务开始时间
        LambdaUpdateWrapper<TOrder> update = Wrappers.lambdaUpdate(TOrder.class)
                .set(TOrder::getStatus, OrderStatusEnum.IN_SERVICE.getCode())
                .set(TOrder::getStartTime, LocalDateTime.now())
                .set(TOrder::getUpdateTime, LocalDateTime.now())
                .set(TOrder::getUpdateBy, loginUser.getCNickName())
                .set(TOrder::getStartPhoto, StrUtil.join(",", dto.getImageUrls()))
                .eq(TOrder::getId, orderId)
                .eq(TOrder::getStatus, status);
        int count = baseMapper.update(null, update);
        if (count <= 0) {
            throw new ServiceException("订单状态已变更，请刷新重试");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void finishService(MerchantOrderOperateDTO dto) {

        // 1. 获取当前登录商户
        WxLoginUser loginUser = getCurrentWxLoginUser();
        Long merchantId = Long.parseLong(loginUser.getId());
        Long orderId = dto.getOrderId();

        TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class)
                .select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus, TOrder::getStartTime)
                .eq(TOrder::getId, orderId)
                .eq(TOrder::getMerchantId, merchantId)
                .eq(TOrder::getIsDelete, NOT_DELETE));
        if (ObjectUtil.isNull(order)) {
            throw new ServiceException("订单不存在或不属于当前商户");
        }

        Integer status = order.getStatus();
        if (!OrderStatusEnum.IN_SERVICE.getCode().equals(status)) {
            throw new ServiceException("仅【服务中】订单可完成服务，当前状态：" + OrderStatusEnum.getInfoByCode(status));
        }

        // 必须存在服务开始时间才能结束
        if (ObjectUtil.isNull(order.getStartTime())) {
            throw new ServiceException("未开始服务，无法完成");
        }

        // 更新状态、结束时间
        LambdaUpdateWrapper<TOrder> update = Wrappers.lambdaUpdate(TOrder.class)
                .set(TOrder::getStatus, OrderStatusEnum.COMPLETED.getCode())
                .set(TOrder::getCompletedTime, LocalDateTime.now())
                .set(TOrder::getUpdateTime, LocalDateTime.now())
                .eq(TOrder::getId, orderId)
                .set(TOrder::getUpdateBy, loginUser.getCNickName())
                .eq(TOrder::getStatus, status);
        int count = baseMapper.update(null, update);
        if (count <= 0) {
            throw new ServiceException("订单状态已变更，请刷新重试");
        }

        // 商户表 n_num 字段原子性自增 1
        LambdaUpdateWrapper<MaTechnician> techUpdate = Wrappers.lambdaUpdate(MaTechnician.class)
                .setSql("n_num = n_num + 1")
                .eq(MaTechnician::getId, merchantId);

        maTechnicianMapper.update(null, techUpdate);
        // Redis商户ZSet score +1
        redisTemplate.opsForZSet().incrementScore("hot:merchant:rank", String.valueOf(order.getMerchantId()), 1);

        // 项目表 projectUpdate 字段原子性自增 1
        LambdaUpdateWrapper<Project> projectUpdate = Wrappers.lambdaUpdate(Project.class)
                .setSql("sales_completed = sales_completed + 1")
                .eq(Project::getId, order.getProjectId());

        this.projectService.update(null, projectUpdate);
        // Redis项目ZSet score +1
        redisTemplate.opsForZSet().incrementScore("hot:project:rank", String.valueOf(order.getProjectId()), 1);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void merchantArriveSign(MerchantOrderOperateDTO dto) {

        // 1. 获取当前登录商户
        WxLoginUser loginUser = getCurrentWxLoginUser();
        Long merchantId = Long.parseLong(loginUser.getId());
        Long orderId = dto.getOrderId();
        String code = dto.getCode();
        String phone = dto.getPhone();

        // 手机号或者验证码 校验
        if (ObjectUtil.hasNull(phone, code)) {
            throw new ServiceException("手机号或者验证码不能为空");
        }

        if (!validateVerifyCode(phone, code)) {
            throw new ServiceException("验证码错误");
        }

        // 查询订单
        TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class)
                .select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus)
                .eq(TOrder::getId, orderId)
                .eq(TOrder::getMerchantId, merchantId)
                .eq(TOrder::getIsDelete, NOT_DELETE));
        if (ObjectUtil.isNull(order)) {
            throw new ServiceException("订单不存在或不属于当前商户");
        }

        // 状态仅允许待服务
        Integer status = order.getStatus();
        if (!OrderStatusEnum.PENDING_SERVICE.getCode().equals(status)) {
            throw new ServiceException("仅【待服务】订单可签到到达，当前状态：" + OrderStatusEnum.getInfoByCode(status));
        }

        // 更新到达信息
        LocalDateTime now = LocalDateTime.now();
        LambdaUpdateWrapper<TOrder> update = Wrappers.lambdaUpdate(TOrder.class)
                .set(TOrder::getArrivedTime, now)
                .set(TOrder::getSignTime, now)
                .set(TOrder::getUpdateTime, now)
                .set(TOrder::getUpdateBy, loginUser.getCNickName())
                .set(TOrder::getArrivalPhoto, StrUtil.join(",", dto.getImageUrls()))
                .eq(TOrder::getId, orderId)
                .eq(TOrder::getStatus, status);
        int count = baseMapper.update(null, update);
        if (count <= 0) {
            throw new ServiceException("订单状态已变更，请刷新重试");
        }
    }

    @Override
    public Page<RecentMerchantVO> getRecentMerchants(RecentOrderQueryDTO dto) {

        // 1. 获取当前登录用户
        WxLoginUser wxLoginUser = this.getCurrentWxLoginUser();
        Long userId = Long.parseLong(wxLoginUser.getId());
        dto.setUserId(userId);

        // 2. 构建分页对象
        Page<RecentMerchantVO> page = new Page<>(dto.getCurrent(), dto.getSize());
        page = this.baseMapper.getRecentMerchants(page, dto);

        return page;
    }

    @Override
    public MerchantOrderDetailVO getMerchantOrderDetail(MerchantOrderDetailDTO dto) {

        // 1. 根据ID查询订单主表
        TOrder order = this.baseMapper.selectById(dto.getOrderId());
        if (ObjectUtil.isNull(order)) {
            throw new ServiceException("订单不存在");
        }

        if (ObjectUtil.notEqual(order.getIsDelete(), NOT_DELETE)) {
            throw new ServiceException("订单已被删除");
        }

        MerchantOrderDetailVO vo = new MerchantOrderDetailVO<>();

        // 2. 基础字段映射 (使用 BeanUtil 或手动 set)
        BeanUtil.copyProperties(order, vo);
        vo.setOrderStatus(order.getStatus());
        vo.setOrderStatusName(OrderStatusEnum.getInfoByCode(order.getStatus()));

        // 特殊处理 ID 转 String (前端 Long 精度丢失问题)
        vo.setOrderId(String.valueOf(order.getId()));

        // 格式化时间范围 (例如: 5月12日 16:00-18:00)
        vo.setAppointmentTimeRange(formatTimeRange(order.getAppointmentStartTime(), order.getProjectDuration()));

        // 3. 联系信息脱敏处理
        vo.setContactPhoneNumber(PhoneUtils.maskPhoneNumber(order.getContactPhoneNumber()));

        // 4. 计算距离与告警提示 (依赖地理围栏表 t_geo_fence)
        List<TGeoFence> validFenceList = geoFenceService.selectValidFences(order.getCityCode());
        List<TGeoFence> usableFence = filterUsableFence(validFenceList);


        // 5. 查询关联的售后信息 (如果有)
        fillCurrentAfterSaleInfo(vo, order.getId());
        // 6. 计算高风险围栏信息
        calcOrderGeoFenceInfo(vo, usableFence);

        return vo;
    }

    @Override
    public List<BookingSlotsVO> bookingSlots(BookingSlotsDTO dto) {

        // 1. 获取项目信息（关键：服务时长）
        Project project = this.projectService.getById(dto.getProjectId());
        if (ObjectUtil.isNull(project)) {
            throw new ServiceException("项目不存在");
        }

        Integer standardDuration = project.getStandardDuration();
        Integer unitType = project.getUnitType();
        String unitTypeName = "未知单位";
        List<SysDictData> dictList = DictUtils.getSortedDictCache(DICT_UNIT_TYPE);
        if (CollUtil.isNotEmpty(dictList) && ObjectUtil.isNotNull(unitType)) {
            unitTypeName = dictList.stream()
                    .filter(dict -> unitType.toString().equals(dict.getDictValue()))
                    .map(SysDictData::getDictLabel)
                    .findFirst()
                    .orElse("未知单位");
        }

        Integer durationMinutes = convertToMinutes(standardDuration, unitType);
        if (ObjectUtil.isNull(durationMinutes) || durationMinutes <= 0) {
            throw new ServiceException("项目时长配置异常");
        }

        // 2. 生成所有候选开始时间（半点粒度，步进30分钟）
        List<LocalTime> candidateStarts = TimeSlotUtil.getAvailableTimeSlots(dto.getAppointmentDate(), 30);

        // 3. 查询该商户当天的【有效占用】订单
        LocalDateTime dayStart = dto.getAppointmentDate().atStartOfDay();
        LocalDateTime dayEnd = dto.getAppointmentDate().atTime(LocalTime.MAX);
        List<TOrder> validOrders = this.baseMapper.selectValidOrdersByMerchantAndDate(
                dto.getMerchantId(), dto.getProjectId(), dayStart, dayEnd);

        // 4. 遍历候选时段，构建 VO
        List<BookingSlotsVO> voList = new ArrayList<>(candidateStarts.size());
        for (LocalTime startTime : candidateStarts) {
            LocalDateTime slotStart = LocalDateTime.of(dto.getAppointmentDate(), startTime);
            LocalDateTime actualEnd = slotStart.plusMinutes(durationMinutes);

            BookingSlotsVO vo = new BookingSlotsVO();
            vo.setStartTime(startTime);
            vo.setStandardDuration(standardDuration);
            vo.setUnitTypeName(unitTypeName);
            vo.setEndTime(actualEnd.toLocalTime());
            vo.setIsDay(maProjectFareSettingService.isDayTimePeriod(slotStart));

            boolean exceedsDay = !actualEnd.toLocalDate().isEqual(dto.getAppointmentDate());
            vo.setAvailable(!exceedsDay && isAppointmentSlotAvailable(slotStart, durationMinutes, validOrders));
            voList.add(vo);
        }

        return voList;
    }

    /**
     * 判断预约时段是否与已有订单冲突（含30分钟缓冲）
     */
    private boolean isAppointmentSlotAvailable(LocalDateTime newStart, int durationMinutes, List<TOrder> validOrders) {
        LocalDateTime actualEnd = newStart.plusMinutes(durationMinutes);
        LocalDateTime newEndWithBuffer = actualEnd.plusMinutes(APPOINT_BUFFER_MINUTES);
        for (TOrder order : validOrders) {
            LocalDateTime existStart = order.getAppointmentStartTime();
            LocalDateTime existEnd = order.getAppointmentEndTime();
            if (newStart.isBefore(existEnd) && newEndWithBuffer.isAfter(existStart)) {
                return false;
            }
        }
        return true;
    }

    private void fillCurrentAfterSaleInfo(IAfterSaleDisplay vo, Long orderId) {
        LambdaQueryWrapper<AfterSalesService> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AfterSalesService::getOrderId, orderId).orderByDesc(AfterSalesService::getCreateTime).last("LIMIT 1");

        AfterSalesService afterSalesService = this.afterSalesServiceService.getOne(wrapper);
        if (ObjectUtil.isNull(afterSalesService)) {
            return;
        }

        vo.setAfterSalesServiceId(afterSalesService.getId());
        Integer statusCode = afterSalesService.getStatus();
        if (ObjectUtil.isNotNull(statusCode)) {
            AfterSaleServiceStatusEnum statusEnum = AfterSaleServiceStatusEnum.valueOf(statusCode);
            vo.setAfterSalesServiceStatus(ObjectUtil.isNotNull(statusEnum) ? statusEnum.getDesc() : "未知状态");
        } else {
            vo.setAfterSalesServiceStatus("未知状态");
        }
        vo.setActualRefundAmount(afterSalesService.getActualRefundAmount());

    }

    /**
     * 根据单位类型将时长转换为分钟
     *
     * @param duration 原始时长
     * @param unitType 单位类型 (对应字典表: 1-分钟, 2-小时, 3-小时/次)
     * @return 转换后的分钟数
     */
    private Integer convertToMinutes(Integer duration, Integer unitType) {
        // 1. 防御性校验：避免自动拆箱导致的 NPE
        if (duration == null || duration <= 0 || unitType == null) {
            return duration;
        }

        // 2. 从字典缓存中获取数据（若依的 DictUtils 读取成本极低，无需自己做 DCL 锁）
        List<SysDictData> dictList = DictUtils.getSortedDictCache(DICT_UNIT_TYPE);
        if (CollUtil.isEmpty(dictList)) {
            return duration;
        }

        // 3. 使用局部变量构建映射，彻底杜绝多线程并发修改 HashMap 的风险
        Map<Integer, Integer> localCoeffMap = new HashMap<>(dictList.size());
        for (SysDictData data : dictList) {
            try {
                int type = Integer.parseInt(data.getDictValue());
                // 根据业务规则设置倍率
                int ratio = (type == 1) ? 1 : 60;
                localCoeffMap.put(type, ratio);
            } catch (NumberFormatException e) {
                log.warn("字典数据格式错误，跳过该条记录: {}", data);
            }
        }

        // 4. 获取倍率并计算，默认倍率为 1（即不转换）
        int factor = localCoeffMap.getOrDefault(unitType, 1);
        return duration * factor;
    }

    /**
     * 计算订单分佣金额
     * 核心财务原则：先算出其中一方收益，另一方用总金额相减，避免精度丢失导致 1 分钱误差
     *
     * @param order              订单对象
     * @param finalAmount        最终实付金额
     * @param merchantShareRatio 商户分佣比例(%)
     */
    private void calculateOrderIncome(TOrder order, BigDecimal finalAmount, BigDecimal merchantShareRatio) {
        if (ObjectUtil.isNull(finalAmount) || finalAmount.compareTo(BigDecimal.ZERO) <= 0) {
            order.setPlatformIncome(BigDecimal.ZERO);
            order.setMerchantIncome(BigDecimal.ZERO);
            return;
        }

        // 防止分佣比例为空
        BigDecimal ratio = Optional.ofNullable(merchantShareRatio).orElse(BigDecimal.ZERO);
        BigDecimal hundred = new BigDecimal("100");

        // 1. 计算商户收益 = 实付金额 * 商户比例 / 100，保留两位小数，四舍五入
        BigDecimal merchantIncome = finalAmount.multiply(ratio).divide(hundred, 2, RoundingMode.HALF_UP);

        // 2. 计算平台收益 = 实付金额 - 商户收益
        BigDecimal platformIncome = finalAmount.subtract(merchantIncome);

        // 3. 赋值给订单对象
        order.setMerchantIncome(merchantIncome);
        order.setPlatformIncome(platformIncome);
    }


    private WxLoginUser getCurrentWxLoginUser() {
        WxLoginUser loginUser = SecurityUtils.getWxLoginUser();
        if (ObjectUtil.isNull(loginUser)) {
            throw new ServiceException("用户未登录或登录已过期");
        }
        return loginUser;
    }

    /**
     * 抽取公共校验逻辑：校验订单是否存在、是否被删除、是否属于当前商户
     */
    private TOrder validateAndGetOrder(Long orderId, Long merchantId) {
        if (ObjectUtil.isNull(orderId)) {
            throw new IllegalArgumentException("订单ID不能为空");
        }

        TOrder order = this.baseMapper.selectById(orderId);
        if (ObjectUtil.isNull(order)) {
            throw new ServiceException("订单不存在");
        }

        if (ObjectUtil.notEqual(order.getIsDelete(), NOT_DELETE)) {
            throw new ServiceException("订单已被删除");
        }

        if (ObjectUtil.notEqual(order.getMerchantId(), merchantId)) {
            throw new ServiceException("您无权操作此订单");
        }

        return order;
    }

    /**
     * 校验验证码
     *
     * @param phone 手机号
     * @param code  用户输入的验证码
     * @return boolean 校验是否通过
     */
    private boolean validateVerifyCode(String phone, String code) {
        if (sendSmsEnabled) {
            // TODO: 从Redis或其他存储中获取真实发送的验证码进行校验
            // String realCode = redisCache.getCacheObject("sms:bindPhone:" + phone);
            // return code.equals(realCode);
            return true;
        } else {
            // 使用写死的验证码进行校验
            return fixedVerifyCode.equals(code);
        }
    }

    /**
     * 计算用户客户标签编码
     *
     * @param vo         当前订单VO
     * @param merchantId 当前商户ID
     * @return CustomerTagEnum.code
     */
    private Integer calcCustomerTagCode(OrderPageVO vo, Long merchantId, LocalDateTime currentTime) {
        Long userId = vo.getUserId();
        Long categoryId = vo.getCategoryId();
        // 关键参数缺失，返回null
        if (ObjectUtil.hasNull(userId, categoryId, currentTime)) {
            return null;
        }
        // 查询该用户、商户、分类下，当前订单之前最近一笔下单时间
        LocalDateTime lastOrderTime = this.baseMapper.getLastOrderDateBeforeCurrent(userId, merchantId, categoryId, currentTime);
        // 1. 无历史订单 = 新客收单 code=0
        if (ObjectUtil.isNull(lastOrderTime)) {
            return CustomerTagEnum.NEW_CUSTOMER.getCode();
        }
        // 计算自然日间隔
        LocalDate currentDate = currentTime.toLocalDate();
        LocalDate lastDate = lastOrderTime.toLocalDate();
        long dayDiff = ChronoUnit.DAYS.between(lastDate, currentDate);

        // 2. 间隔≤14天：再次复购 code=1
        if (dayDiff <= REPURCHASE_DAY_LIMIT) {
            return CustomerTagEnum.REPURCHASE.getCode();
        }
        // 3. 间隔＞15天：老客户 code=2
        return CustomerTagEnum.OLD_CUSTOMER.getCode();
    }


    /**
     * 获取商户默认地址，无地址直接抛业务异常
     */
    private TAddress getMerchantDefaultAddress(Long merchantId) {
        LambdaQueryWrapper<TAddress> addrWrapper = Wrappers.lambdaQuery(TAddress.class)
                .eq(TAddress::getMerchantId, merchantId)
                .eq(TAddress::getUserType, USER_TYPE_MERCHANT)
                .eq(TAddress::getIsDefault, ADDR_DEFAULT_YES)
                .eq(TAddress::getIsDelete, NOT_DELETE);
        List<TAddress> merchantAddressList = addressService.list(addrWrapper);
        if (CollUtil.isEmpty(merchantAddressList)) {
            throw new ServiceException("商户地址不存在，请先完善商户地址");
        }
        return CollUtil.getFirst(merchantAddressList);
    }

    /**
     * 将商户经纬度填充至查询DTO，坐标为空置null
     */
    private void fillMerchantLonLatToDto(OrderDateQueryDTO dto, TAddress merchantAddress) {
        BigDecimal lat = merchantAddress.getLatitude();
        BigDecimal lon = merchantAddress.getLongitude();
        if (ObjectUtil.hasNull(lat, lon)) {
            log.warn("商户id:{} 默认地址经纬度为空，无法计算围栏距离", dto.getMerchantId());
            dto.setLatitude(null);
            dto.setLongitude(null);
            return;
        }
        dto.setLatitude(lat);
        dto.setLongitude(lon);
    }

    /**
     * 过滤可用围栏：剔除坐标/半径为空、半径<=0的无效围栏，减少内层循环次数
     */
    private List<TGeoFence> filterUsableFence(List<TGeoFence> allFence) {
        if (CollUtil.isEmpty(allFence)) {
            return Collections.emptyList();
        }
        return allFence.stream()
                .filter(fence -> !ObjectUtil.hasNull(fence.getLatitude(), fence.getLongitude(), fence.getRadiusKm()))
                .filter(fence -> fence.getRadiusKm().compareTo(BigDecimal.ZERO) > 0)
                .collect(Collectors.toList());
    }

    /**
     * 计算单条订单围栏距离、是否高风险区域、围栏备注
     */
    private void calcOrderGeoFenceInfo(IGeoRiskInfo orderVO, List<TGeoFence> usableFence) {
        // 用户坐标为空直接标记非风险
        BigDecimal userLat = orderVO.getUserLatitude();
        BigDecimal userLon = orderVO.getUserLongitude();
        if (ObjectUtil.hasNull(userLat, userLon) || CollUtil.isEmpty(usableFence)) {
            orderVO.setIsHighRiskArea(false);
            orderVO.setDistanceKm(null);
            orderVO.setFenceIntro(null);
            return;
        }

        BigDecimal minDistanceKm = null;
        TGeoFence nearestFence = null;
        // 遍历可用围栏，计算最小距离围栏
        for (TGeoFence fence : usableFence) {
            BigDecimal distKm = DistanceUtil.formatDistanceInKilometers(userLat, userLon, fence.getLatitude(), fence.getLongitude());
            if (ObjectUtil.isNull(distKm)) {
                continue;
            }
            // 更新最近围栏
            if (ObjectUtil.isNull(minDistanceKm) || distKm.compareTo(minDistanceKm) < 0) {
                minDistanceKm = distKm;
                nearestFence = fence;
            }
        }

        // 赋值围栏相关信息
        if (ObjectUtil.isNull(minDistanceKm) || ObjectUtil.isNull(nearestFence)) {
            orderVO.setIsHighRiskArea(false);
            orderVO.setDistanceKm(null);
            orderVO.setFenceIntro(null);
            return;
        }
        orderVO.setDistanceKm(minDistanceKm);
        // 判断是否在围栏半径内
        boolean isHighRisk = minDistanceKm.compareTo(nearestFence.getRadiusKm()) <= 0;
        orderVO.setIsHighRiskArea(isHighRisk);
        orderVO.setFenceIntro(nearestFence.getFenceIntro());
    }

    /**
     * 格式化预约时间范围
     * 示例输出: "5月12日 16:00-18:00"
     *
     * @param startTime       预约开始时间
     * @param durationMinutes 项目时长（分钟）
     * @return 格式化后的时间范围字符串
     */
    private String formatTimeRange(LocalDateTime startTime, Integer durationMinutes) {
        if (startTime == null || durationMinutes == null || durationMinutes <= 0) {
            return "";
        }

        LocalDateTime endTime = startTime.plusMinutes(durationMinutes);
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M月d日", Locale.CHINA);
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");

        String dateStr = dateFormatter.format(startTime);
        String startTimeStr = timeFormatter.format(startTime);
        String endTimeStr = timeFormatter.format(endTime);

        // 判断是否跨天
        if (!startTime.toLocalDate().isEqual(endTime.toLocalDate())) {
            // 跨天场景：在结束时间前加上日期
            String endDateStr = dateFormatter.format(endTime);
            return String.format("%s %s-%s %s", dateStr, startTimeStr, endDateStr, endTimeStr);
            // 输出示例: "5月12日 23:00-5月13日 01:00"
        }

        // 同天场景：正常拼接
        return String.format("%s %s-%s", dateStr, startTimeStr, endTimeStr);
    }

}
