package com.ylx.massage.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ylx.common.exception.ServiceException;
import com.ylx.massage.domain.TOrder;
import com.ylx.massage.enums.OrderStatusEnum;
import com.ylx.massage.service.OrderValidationService;
import com.ylx.massage.service.TOrderService;
import com.ylx.massage.utils.OrderTimeRangeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;

/**
 * 订单校验服务实现类
 * <p>
 * 实现订单创建和接单时的校验功能，基于订单状态锁机制。
 * 确保技师服务时间的互斥性，防止冲突订单的产生。
 * </p>
 *
 * @author ylx
 * @version 1.0
 * @since 2024
 */
@Service
public class OrderValidationServiceImpl implements OrderValidationService {

    private static final Logger log = LoggerFactory.getLogger(OrderValidationServiceImpl.class);

    /**
     * 进行中的订单状态列表
     * 包括：待接单、已接单、已出发、已到达、服务中
     */
    private static final List<Integer> IN_PROGRESS_STATUSES = Arrays.asList(
            OrderStatusEnum.WAIT_JD.getCode(),       // 0 - 待接单
        OrderStatusEnum.RECEIVED_ORDER.getCode(), // 1 - 已接单
            OrderStatusEnum.DEPART.getCode(),      // 6 - 已出发
        OrderStatusEnum.ARRIVED.getCode(),        // 2 - 已到达
        OrderStatusEnum.SERVICE.getCode()         // 3 - 服务中
    );

    private final TOrderService orderService;

    public OrderValidationServiceImpl(TOrderService orderService) {
        this.orderService = orderService;
    }

    /**
     * 检查技师是否可以接单
     * <p>
     * 基于订单状态锁机制和时间范围校验，检查技师是否有进行中的订单。
     * 如果技师有进行中的订单，进一步检查时间范围是否重叠。
     * </p>
     * <p>
     * 时间范围校验逻辑：
     * 1. 如果现有订单有明确的时间范围（开始时间、结束时间），检查是否与新订单重叠
     * 2. 如果现有订单时间信息不完整，采用保守策略，拒绝接单
     * 3. 考虑30分钟缓冲时间，在两个订单之间留出缓冲期
     * </p>
     *
     * @param technicianId 技师ID
     * @param newOrder 新订单信息
     * @return boolean true-可以接单
     * @throws ServiceException 当技师不能接单时抛出异常
     */
    @Override
    public boolean canAcceptOrder(String technicianId, TOrder newOrder) {
        // 1. 参数校验
        if (StrUtil.isBlank(technicianId)) {
            throw new ServiceException("技师ID不能为空");
        }

        // 2. 升级订单特殊处理（升级订单可以创建）
        if (OrderTimeRangeUtils.isUpgradeOrder(newOrder)) {
            log.info("升级订单，跳过互斥校验，订单号：{}", newOrder.getOrderNo());
            return true;
        }

        // 3. 查询技师的进行中订单
        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TOrder::getcJsId, technicianId)
                .in(TOrder::getnStatus, IN_PROGRESS_STATUSES)
                .eq(TOrder::getIsDelete, 0);

        List<TOrder> inProgressOrders = orderService.list(queryWrapper);

        // 4. 如果没有进行中的订单，可以接单
        if (inProgressOrders == null || inProgressOrders.isEmpty()) {
            log.info("技师 {} 没有进行中的订单，可以接单", technicianId);
            return true;
        }

        // 5. 有进行中的订单，进一步检查时间范围
        log.info("技师 {} 有 {} 个进行中的订单，开始时间范围校验", technicianId, inProgressOrders.size());

        //新订单的开始和结束时间
        /*LocalDateTime newOrderStart = OrderTimeRangeUtils.estimateStartTime(newOrder);
        LocalDateTime newOrderEnd = OrderTimeRangeUtils.estimateEndTime(newOrder);*/
        String startTimeStr = newOrder.getStartTimeStr();
        LocalDateTime newOrderStart = LocalDateTime.parse(startTimeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        String endTimeStr = newOrder.getEndTimeStr();
        LocalDateTime newOrderEnd = LocalDateTime.parse(endTimeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

        // 创建临时订单对象用于时间范围校验
        TOrder newOrderWithTime = new TOrder();
        newOrderWithTime.setcGoods(newOrder.getcGoods());
        newOrderWithTime.setStartTime(newOrderStart);
        newOrderWithTime.setEndTime(newOrderEnd);

        // 遍历所有进行中的订单，检查时间范围是否重叠
        for (TOrder existingOrder : inProgressOrders) {
            if (OrderTimeRangeUtils.isTimeRangeOverlapping(newOrderWithTime, existingOrder)) {
                // 时间范围重叠，构建详细错误信息
                String message = buildTimeConflictMessage(existingOrder, newOrderWithTime);
                log.warn("技师 {} 无法接单，原因：{}", technicianId, message);
                throw new ServiceException(message);
            }
        }

        // 6. 所有进行中的订单都与新订单时间不冲突，可以接单
        log.info("技师 {} 所有进行中订单的时间范围与新订单不冲突，可以接单", technicianId);
        return true;
    }

    /**
     * 检查技师是否可以被手动分配订单
     * <p>
     * 用于后台管理员手动分配订单的场景。
     * 与接单校验逻辑相同，技师不能有进行中的订单，且时间范围不能重叠。
     * </p>
     *
     * @param technicianId 技师ID
     * @return true-可以分配
     * @throws ServiceException 当技师不能被分配订单时抛出异常
     */
    @Override
    public boolean canManualAssign(String technicianId) {
        // 1. 参数校验
        if (StrUtil.isBlank(technicianId)) {
            throw new ServiceException("技师ID不能为空");
        }

        // 2. 查询技师的进行中订单
        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TOrder::getcJsId, technicianId)
                .in(TOrder::getnStatus, IN_PROGRESS_STATUSES)
                .eq(TOrder::getIsDelete, 0);

        List<TOrder> inProgressOrders = orderService.list(queryWrapper);

        // 3. 如果没有进行中的订单，可以分配
        if (inProgressOrders == null || inProgressOrders.isEmpty()) {
            log.info("技师 {} 没有进行中的订单，可以手动分配订单", technicianId);
            return true;
        }

        // 4. 有进行中的订单，检查时间范围
        log.info("技师 {} 有 {} 个进行中的订单，无法手动分配", technicianId, inProgressOrders.size());

        TOrder currentOrder = inProgressOrders.get(0);
        String statusDesc = OrderStatusEnum.getDescByCode(currentOrder.getnStatus());

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        StringBuilder message = new StringBuilder();
        message.append(String.format("该技师当前有%s的订单（订单号：%s），无法手动分配。\n",
            statusDesc, currentOrder.getOrderNo()));

        // 添加时间信息
        if (currentOrder.getStartTime() != null) {
            message.append("开始时间：").append(currentOrder.getStartTime().format(formatter)).append("\n");
        }
        if (currentOrder.getEndTime() != null) {
            message.append("结束时间：").append(currentOrder.getEndTime().format(formatter)).append("\n");
        }

        message.append("请等待当前订单完成后再试。");

        log.warn("技师 {} 无法手动分配订单，原因：{}", technicianId, message);
        throw new ServiceException(message.toString());
    }

    /**
     * 获取技师的进行中订单数量
     * <p>
     * 统计技师当前进行中的订单数量。
     * 用于前端展示技师状态和负载情况。
     * </p>
     *
     * @param technicianId 技师ID
     * @return 进行中的订单数量
     */
    @Override
    public long getInProgressOrderCount(String technicianId) {
        if (StrUtil.isBlank(technicianId)) {
            return 0;
        }

        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TOrder::getcJsId, technicianId)
                .in(TOrder::getnStatus, IN_PROGRESS_STATUSES)
                .eq(TOrder::getIsDelete, 0);

        return orderService.count(queryWrapper);
    }

    /**
     * 检查订单是否可以支付
     * <p>
     * 防止在订单状态发生变化后仍然可以支付。
     * 只有待付款状态的订单才可以支付。
     * </p>
     *
     * @param orderNo 订单号
     * @return true-可以支付
     * @throws ServiceException 当订单不可支付时抛出异常
     */
    @Override
    public boolean canPayOrder(String orderNo) {
        if (StrUtil.isBlank(orderNo)) {
            throw new ServiceException("订单号不能为空");
        }

        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TOrder::getOrderNo, orderNo)
                .eq(TOrder::getIsDelete, 0);

        TOrder order = orderService.getOne(queryWrapper);

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

        // 只有待付款状态可以支付
        if (!OrderStatusEnum.WAIT_PAY.getCode().equals(order.getnStatus())) {
            String currentStatus = OrderStatusEnum.getDescByCode(order.getnStatus());
            throw new ServiceException(
                String.format("订单当前状态为【%s】，无法支付。只有待付款状态的订单才可以支付。", currentStatus)
            );
        }

        return true;
    }

    /**
     * 构建时间冲突的详细错误信息
     * <p>
     * 当检测到时间范围重叠时，生成包含详细时间信息的错误消息。
     * </p>
     *
     * @param existingOrder 现有订单
     * @param newOrder 新订单
     * @return String 错误信息
     */
    private String buildTimeConflictMessage(TOrder existingOrder, TOrder newOrder) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        StringBuilder message = new StringBuilder();
        message.append("该技师当前有进行中的订单，时间冲突。");

        // 现有订单信息
        message.append("现有订单：").append(existingOrder.getOrderNo()).append("\n");
        if (existingOrder.getStartTime() != null) {
            message.append("  开始时间：").append(existingOrder.getStartTime().format(formatter)).append("\n");
        }
        if (existingOrder.getEndTime() != null) {
            message.append("  结束时间：").append(existingOrder.getEndTime().format(formatter)).append("\n");
        }

        String statusDesc = OrderStatusEnum.getDescByCode(existingOrder.getnStatus());
        message.append("  订单状态：").append(statusDesc).append("\n");

        // 新订单信息
        message.append("新订单：").append(newOrder.getOrderNo()).append(" ");
        if (newOrder.getStartTime() != null) {
            message.append("  预计开始时间：").append(newOrder.getStartTime().format(formatter)).append("\n");
        }
        if (newOrder.getEndTime() != null) {
            message.append("  预计结束时间：").append(newOrder.getEndTime().format(formatter)).append("\n");
        }
        // 提示用户
        Long durationMinutes = OrderTimeRangeUtils.calculateOrderDuration(newOrder);
        if (durationMinutes != null) {
            message.append(" 建议：请等待当前订单完成后再试，或选择其他时间段（需间隔至少30分钟）。");
        }
        return message.toString();
    }
}
