package cn.nianxx.thhotel.workflows.reservation.service.impl;

import cn.nianxx.thhotel.platform.common.time.UtcTimeFormatter;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextService;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextException;
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageSummaryResponse;
import cn.nianxx.thhotel.platform.message.service.SourceMessageQueryService;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiQueryOrderSnapshot;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiQueryTaskSnapshot;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationPageSnapshot;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskSnapshot;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderKeyType;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderVisibility;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationTaskStatus;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationOrderListQueryRequest;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationTaskWorkbenchQueryRequest;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderDetailResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderListItemResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderListResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderSummaryResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderTaskTimelineItemResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationPaginationResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskAvailabilityResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskWorkbenchItemResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskWorkbenchListResult;
import cn.nianxx.thhotel.workflows.reservation.repository.ReservationAiWorkflowRepository;
import cn.nianxx.thhotel.workflows.reservation.service.ReservationFrontendQueryService;
import java.time.OffsetDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Reservation 前端查询服务实现。只组装安全摘要和实时可处理状态，不返回 AI 原始 payload。
 */
@Service
public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQueryService {

    private static final int DEFAULT_PAGE_NUM = 1;
    private static final int DEFAULT_PAGE_SIZE = 20;
    private static final int MAX_PAGE_SIZE = 100;
    private static final String REASON_PROCESSABLE = "PROCESSABLE";
    private static final String REASON_PREVIOUS_TASK_NOT_FINISHED = "PREVIOUS_TASK_NOT_FINISHED";
    private static final String REASON_TASK_FINISHED = "TASK_FINISHED";
    private static final String REASON_READ_ONLY = "READ_ONLY";

    private final ReservationAiWorkflowRepository workflowRepository;
    private final SourceMessageQueryService sourceMessageQueryService;
    private final ReservationTaskAvailabilityResolver availabilityResolver;
    private final HotelContextService hotelContextService;

    /**
     * 注入持久化边界、SourceMessage 安全摘要服务和可处理状态解析器。
     */
    public ReservationFrontendQueryServiceImpl(
            ReservationAiWorkflowRepository workflowRepository,
            SourceMessageQueryService sourceMessageQueryService,
            ReservationTaskAvailabilityResolver availabilityResolver,
            HotelContextService hotelContextService) {
        this.workflowRepository = workflowRepository;
        this.sourceMessageQueryService = sourceMessageQueryService;
        this.availabilityResolver = availabilityResolver;
        this.hotelContextService = hotelContextService;
    }

    /**
     * 查询前端任务列表，并补充订单展示键和来源消息安全主题摘要。
     */
    @Override
    @Transactional(readOnly = true)
    public ReservationTaskWorkbenchListResult queryTaskWorkbench(ReservationTaskWorkbenchQueryRequest request) {
        ReservationTaskWorkbenchQueryRequest normalizedRequest = normalizeRequest(request);
        int pageNum = normalizePageNum(normalizedRequest.pageNum());
        int pageSize = normalizePageSize(normalizedRequest.pageSize());
        List<Long> keywordSourceMessageIds = findSourceMessageIdsByKeyword(
                normalizedRequest.hotelId(),
                normalizedRequest.keyword());
        ReservationPageSnapshot<ReservationAiQueryTaskSnapshot> page =
                workflowRepository.queryFrontendTasks(normalizedRequest, keywordSourceMessageIds, pageNum, pageSize);
        Map<Long, ReservationAiQueryOrderSnapshot> ordersById = findOrdersById(
                normalizedRequest.hotelId(),
                page.items().stream().map(ReservationAiQueryTaskSnapshot::orderId).toList());
        Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId = findAvailabilityByTaskId(
                normalizedRequest.hotelId(),
                page.items());
        Map<Long, SourceMessageDisplayContext> sourceContextsById = findSourceContextsById(
                normalizedRequest.hotelId(),
                page.items());
        List<ReservationTaskWorkbenchItemResult> items = page.items().stream()
                .map(task -> toWorkbenchItem(
                        task,
                        ordersById.get(task.orderId()),
                        availabilityOrReadOnly(task, availabilityByTaskId),
                        sourceContextsById.get(task.sourceMessageId())))
                .toList();
        return new ReservationTaskWorkbenchListResult(
                items,
                new ReservationPaginationResult(page.pageNum(), page.pageSize(), page.total()));
    }

    /**
     * 查询订单列表，并按当前任务队列实时计算未关闭任务数量和下一条可处理任务。
     */
    @Override
    @Transactional(readOnly = true)
    public ReservationOrderListResult queryOrders(ReservationOrderListQueryRequest request) {
        ReservationOrderListQueryRequest normalizedRequest = normalizeOrderListRequest(request);
        int pageNum = normalizePageNum(normalizedRequest.pageNum());
        int pageSize = normalizePageSize(normalizedRequest.pageSize());
        List<Long> keywordSourceMessageIds = findSourceMessageIdsByKeyword(
                normalizedRequest.hotelId(),
                normalizedRequest.keyword());
        ReservationPageSnapshot<ReservationAiQueryOrderSnapshot> page =
                workflowRepository.queryFrontendOrders(normalizedRequest, keywordSourceMessageIds, pageNum, pageSize);
        List<Long> orderIds = page.items().stream().map(ReservationAiQueryOrderSnapshot::id).toList();
        List<ReservationAiQueryTaskSnapshot> orderTasks = workflowRepository.findAiQueryTasksByOrderIds(
                normalizedRequest.hotelId(),
                orderIds);
        Map<Long, List<ReservationAiQueryTaskSnapshot>> tasksByOrderId = groupTasksByOrderId(orderTasks);
        Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId =
                calculateAvailabilityByTaskId(orderTasks, orderTasks);
        List<ReservationOrderListItemResult> items = page.items().stream()
                .map(order -> toOrderListItem(
                        order,
                        tasksByOrderId.getOrDefault(order.id(), List.of()),
                        availabilityByTaskId))
                .toList();
        return new ReservationOrderListResult(
                items,
                new ReservationPaginationResult(page.pageNum(), page.pageSize(), page.total()));
    }

    /**
     * 查询订单详情。includeTasks=false 时只返回订单摘要，便于后续前端轻量使用。
     */
    @Override
    @Transactional(readOnly = true)
    public ReservationOrderDetailResult getOrderDetail(
            String hotelId,
            Long orderId,
            Boolean includeTasks,
            Boolean includeSourceSummary) {
        String normalizedHotelId = normalizeHotelId(hotelId);
        ReservationAiQueryOrderSnapshot order = workflowRepository
                .findAiQueryOrderById(normalizedHotelId, orderId)
                .orElseThrow(() -> new ReservationTaskWorkflowException(
                        HttpStatus.NOT_FOUND,
                        "ORDER_NOT_FOUND",
                        "订单不存在。"));
        if (isHiddenSystemOrder(order)) {
            throw new ReservationTaskWorkflowException(
                    HttpStatus.NOT_FOUND,
                    "ORDER_NOT_FOUND",
                    "订单不存在。");
        }
        List<ReservationAiQueryTaskSnapshot> taskSnapshots = Boolean.FALSE.equals(includeTasks)
                ? List.of()
                : workflowRepository.findAiQueryTasksByOrderIds(normalizedHotelId, List.of(order.id()));
        Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId = calculateAvailabilityByTaskId(
                taskSnapshots,
                taskSnapshots);
        Map<Long, SourceMessageDisplayContext> sourceContextsById = findSourceContextsById(
                normalizedHotelId,
                taskSnapshots);
        List<ReservationOrderTaskTimelineItemResult> tasks = taskSnapshots.stream()
                .map(task -> toTimelineItem(
                        task,
                        availabilityOrReadOnly(task, availabilityByTaskId),
                        sourceContextsById.get(task.sourceMessageId())))
                .toList();
        return new ReservationOrderDetailResult(toOrderSummary(order), tasks, List.of());
    }

    /**
     * 标准化任务列表查询条件，避免 Controller 层散落默认值规则。
     */
    private ReservationTaskWorkbenchQueryRequest normalizeRequest(ReservationTaskWorkbenchQueryRequest request) {
        if (request == null) {
            return new ReservationTaskWorkbenchQueryRequest(
                    normalizeHotelId(null),
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    DEFAULT_PAGE_NUM,
                    DEFAULT_PAGE_SIZE);
        }
        return new ReservationTaskWorkbenchQueryRequest(
                normalizeHotelId(request.hotelId()),
                request.orderId(),
                trimToNull(request.taskType()),
                trimToNull(request.taskStatus()),
                trimToNull(request.taskSubtype()),
                trimToNull(request.orderStatus()),
                request.queueParticipation(),
                trimToNull(request.keyword()),
                request.pageNum(),
                request.pageSize());
    }

    /**
     * 标准化订单列表查询条件。订单状态为空时保留为空，表示查询全部状态。
     */
    private ReservationOrderListQueryRequest normalizeOrderListRequest(ReservationOrderListQueryRequest request) {
        if (request == null) {
            return new ReservationOrderListQueryRequest(
                    normalizeHotelId(null),
                    null,
                    null,
                    null,
                    null,
                    DEFAULT_PAGE_NUM,
                    DEFAULT_PAGE_SIZE);
        }
        return new ReservationOrderListQueryRequest(
                normalizeHotelId(request.hotelId()),
                trimToNull(request.orderStatus()),
                trimToNull(request.groupCode()),
                trimToNull(request.confirmationNumber()),
                trimToNull(request.keyword()),
                request.pageNum(),
                request.pageSize());
    }

    /**
     * 批量读取订单快照并按订单 ID 建立索引。
     */
    private Map<Long, ReservationAiQueryOrderSnapshot> findOrdersById(String hotelId, List<Long> orderIds) {
        Map<Long, ReservationAiQueryOrderSnapshot> result = new LinkedHashMap<>();
        workflowRepository.findAiQueryOrdersByIds(hotelId, orderIds)
                .forEach(order -> result.put(order.id(), order));
        return result;
    }

    /**
     * 批量计算任务可处理状态，列表页按订单一次性读取队列上下文，避免每条任务单独查询前置任务。
     */
    private Map<Long, ReservationTaskAvailabilityResult> findAvailabilityByTaskId(
            String hotelId,
            List<ReservationAiQueryTaskSnapshot> targetTasks) {
        if (targetTasks == null || targetTasks.isEmpty()) {
            return Map.of();
        }
        List<Long> orderIds = targetTasks.stream()
                .map(ReservationAiQueryTaskSnapshot::orderId)
                .filter(Objects::nonNull)
                .distinct()
                .toList();
        List<ReservationAiQueryTaskSnapshot> queueContextTasks = workflowRepository.findAiQueryTasksByOrderIds(
                hotelId,
                orderIds);
        return calculateAvailabilityByTaskId(targetTasks, queueContextTasks);
    }

    /**
     * 使用已加载的队列上下文批量计算可处理状态，供订单详情复用同一批任务数据。
     */
    private Map<Long, ReservationTaskAvailabilityResult> calculateAvailabilityByTaskId(
            List<ReservationAiQueryTaskSnapshot> targetTasks,
            List<ReservationAiQueryTaskSnapshot> queueContextTasks) {
        return availabilityResolver.calculateAvailabilityByTaskId(
                targetTasks.stream().map(this::toTaskSnapshot).toList(),
                queueContextTasks.stream().map(this::toTaskSnapshot).toList());
    }

    /**
     * 批量读取来源消息主题摘要，避免任务列表按每条任务调用 SourceMessage 详情查询。
     */
    private Map<Long, SourceMessageDisplayContext> findSourceContextsById(
            String hotelId,
            List<ReservationAiQueryTaskSnapshot> tasks) {
        if (tasks == null || tasks.isEmpty()) {
            return Map.of();
        }
        List<Long> sourceMessageIds = tasks.stream()
                .map(ReservationAiQueryTaskSnapshot::sourceMessageId)
                .filter(Objects::nonNull)
                .distinct()
                .toList();
        List<SourceMessageSummaryResponse> summaries = sourceMessageQueryService.getSummariesByIds(sourceMessageIds);
        Map<String, Long> conversationCounts = sourceMessageQueryService.countByExternalConversationIds(
                hotelId,
                summaries.stream()
                        .map(SourceMessageSummaryResponse::externalConversationId)
                        .filter(Objects::nonNull)
                        .distinct()
                        .toList());
        Map<Long, SourceMessageDisplayContext> result = new LinkedHashMap<>();
        for (SourceMessageSummaryResponse summary : summaries) {
            result.put(Long.valueOf(summary.id()), new SourceMessageDisplayContext(
                    summary.subject(),
                    summary.senderSummary(),
                    summary.receivedAt(),
                    summary.externalConversationId(),
                    conversationCounts.get(summary.externalConversationId())));
        }
        return result;
    }

    /**
     * 按来源消息安全摘要关键词查找 SourceMessage ID，避免任务列表直接读取来源消息正文。
     */
    private List<Long> findSourceMessageIdsByKeyword(String hotelId, String keyword) {
        if (trimToNull(keyword) == null) {
            return List.of();
        }
        return sourceMessageQueryService.findIdsBySafeKeyword(hotelId, keyword);
    }

    /**
     * 转换任务快照为前端工作台单行结果。
     */
    private ReservationTaskWorkbenchItemResult toWorkbenchItem(
            ReservationAiQueryTaskSnapshot task,
            ReservationAiQueryOrderSnapshot order,
            ReservationTaskAvailabilityResult availability,
            SourceMessageDisplayContext sourceContext) {
        return new ReservationTaskWorkbenchItemResult(
                task.id().toString(),
                task.orderId().toString(),
                task.hotelId(),
                displayOrderKey(order),
                order == null || isHiddenSystemOrder(order) ? null : order.temporaryOrderCode(),
                task.systemTaskType(),
                task.resultType(),
                task.aiTaskType(),
                task.taskSubtype(),
                task.routeCode(),
                task.systemProcessCategory(),
                task.taskStatus(),
                task.taskCardType(),
                task.executionOrder(),
                task.queueParticipation(),
                canProcess(availability),
                readonlyReasonCode(task, availability),
                task.sourceMessageId().toString(),
                sourceContext == null ? null : sourceContext.subject(),
                sourceContext == null ? null : sourceContext.senderSummary(),
                sourceContext == null ? null : sourceContext.receivedAt(),
                sourceContext == null ? null : sourceContext.externalConversationId(),
                sourceContext == null ? null : sourceContext.conversationMessageCount(),
                UtcTimeFormatter.toUtcOffsetDateTime(task.createdAt()),
                UtcTimeFormatter.toUtcOffsetDateTime(task.updatedAt()));
    }

    /**
     * 转换任务快照为订单详情任务时间线结果。
     */
    private ReservationOrderTaskTimelineItemResult toTimelineItem(
            ReservationAiQueryTaskSnapshot task,
            ReservationTaskAvailabilityResult availability,
            SourceMessageDisplayContext sourceContext) {
        return new ReservationOrderTaskTimelineItemResult(
                task.id().toString(),
                task.systemTaskType(),
                task.resultType(),
                task.aiTaskType(),
                task.taskSubtype(),
                task.routeCode(),
                task.systemProcessCategory(),
                task.taskStatus(),
                task.taskCardType(),
                task.executionOrder(),
                task.queueParticipation(),
                canProcess(availability),
                readonlyReasonCode(task, availability),
                task.sourceMessageId().toString(),
                sourceContext == null ? null : sourceContext.subject(),
                sourceContext == null ? null : sourceContext.senderSummary(),
                sourceContext == null ? null : sourceContext.receivedAt(),
                sourceContext == null ? null : sourceContext.externalConversationId(),
                sourceContext == null ? null : sourceContext.conversationMessageCount(),
                UtcTimeFormatter.toUtcOffsetDateTime(task.createdAt()));
    }

    /**
     * 转换订单快照为前端订单列表单行结果。
     */
    private ReservationOrderListItemResult toOrderListItem(
            ReservationAiQueryOrderSnapshot order,
            List<ReservationAiQueryTaskSnapshot> tasks,
            Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId) {
        return new ReservationOrderListItemResult(
                order.id().toString(),
                order.hotelId(),
                order.orderStatus(),
                displayOrderKey(order),
                order.temporaryOrderCode(),
                groupCode(order),
                confirmationNumber(order),
                order.displayName(),
                openTaskCount(tasks),
                nextProcessableTaskId(tasks, availabilityByTaskId),
                UtcTimeFormatter.toUtcOffsetDateTime(order.createdAt()),
                UtcTimeFormatter.toUtcOffsetDateTime(order.updatedAt()));
    }

    /**
     * 转换订单快照为前端订单摘要。
     */
    private ReservationOrderSummaryResult toOrderSummary(ReservationAiQueryOrderSnapshot order) {
        return new ReservationOrderSummaryResult(
                order.id().toString(),
                order.hotelId(),
                order.orderStatus(),
                order.temporaryOrderCode(),
                confirmationNumber(order),
                groupCode(order),
                null,
                null,
                order.displayName(),
                UtcTimeFormatter.toUtcOffsetDateTime(order.createdAt()),
                UtcTimeFormatter.toUtcOffsetDateTime(order.updatedAt()));
    }

    /**
     * 按订单 ID 对任务分组，并保持数据库返回顺序。
     */
    private Map<Long, List<ReservationAiQueryTaskSnapshot>> groupTasksByOrderId(
            List<ReservationAiQueryTaskSnapshot> tasks) {
        Map<Long, List<ReservationAiQueryTaskSnapshot>> tasksByOrderId = new LinkedHashMap<>();
        for (ReservationAiQueryTaskSnapshot task : tasks) {
            tasksByOrderId.computeIfAbsent(task.orderId(), ignored -> new java.util.ArrayList<>()).add(task);
        }
        return tasksByOrderId;
    }

    /**
     * 统计未关闭任务数量，排除 COMPLETED 和 FAILED。
     */
    private Integer openTaskCount(List<ReservationAiQueryTaskSnapshot> tasks) {
        return Math.toIntExact(tasks.stream()
                .filter(task -> !ReservationTaskStatus.COMPLETED.name().equals(task.taskStatus()))
                .filter(task -> !ReservationTaskStatus.FAILED.name().equals(task.taskStatus()))
                .count());
    }

    /**
     * 找到当前第一条可处理任务。FAILED 已结束，不会阻塞后续任务。
     */
    private String nextProcessableTaskId(
            List<ReservationAiQueryTaskSnapshot> tasks,
            Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId) {
        return tasks.stream()
                .filter(task -> canProcess(availabilityOrReadOnly(task, availabilityByTaskId)))
                .map(task -> task.id().toString())
                .findFirst()
                .orElse(null);
    }

    /**
     * 转换 AI 查询任务快照为可处理状态解析器使用的通用任务快照。
     */
    private ReservationTaskSnapshot toTaskSnapshot(ReservationAiQueryTaskSnapshot task) {
        return new ReservationTaskSnapshot(
                task.id(),
                task.hotelId(),
                task.orderId(),
                task.sourceMessageId(),
                task.aiTransitionId(),
                task.resultType(),
                task.aiTaskType(),
                task.systemTaskType(),
                task.taskCardType(),
                task.taskSubtype(),
                task.taskStatus(),
                task.queueParticipation(),
                task.executionOrder(),
                task.parentTaskId(),
                task.parentSourceEventIndex(),
                task.linkedTaskGroupId(),
                task.blockedUntilParentCompleted(),
                null);
    }

    /**
     * 判断当前任务是否可由前端呈现为可处理状态。
     */
    private boolean canProcess(ReservationTaskAvailabilityResult availability) {
        return availability.editable() || availability.confirmable() || availability.executable();
    }

    /**
     * 兜底返回只读状态；正常情况下批量可处理状态结果应覆盖所有目标任务。
     */
    private ReservationTaskAvailabilityResult availabilityOrReadOnly(
            ReservationAiQueryTaskSnapshot task,
            Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId) {
        ReservationTaskAvailabilityResult availability = availabilityByTaskId.get(task.id());
        if (availability != null) {
            return availability;
        }
        return new ReservationTaskAvailabilityResult(false, true, false, false, false, null, null);
    }

    /**
     * 将实时可处理状态转换为前端稳定原因代码。
     */
    private String readonlyReasonCode(
            ReservationAiQueryTaskSnapshot task,
            ReservationTaskAvailabilityResult availability) {
        if (availability.blocked()) {
            return REASON_PREVIOUS_TASK_NOT_FINISHED;
        }
        if (ReservationTaskStatus.COMPLETED.name().equals(task.taskStatus())
                || ReservationTaskStatus.FAILED.name().equals(task.taskStatus())) {
            return REASON_TASK_FINISHED;
        }
        if (canProcess(availability)) {
            return REASON_PROCESSABLE;
        }
        return REASON_READ_ONLY;
    }

    /**
     * 生成订单展示键，优先真实业务号，其次临时订单号。
     */
    private String displayOrderKey(ReservationAiQueryOrderSnapshot order) {
        if (order == null || isHiddenSystemOrder(order)) {
            return null;
        }
        String activeBusinessKey = trimToNull(order.activeBusinessKey());
        if (activeBusinessKey != null) {
            return activeBusinessKey;
        }
        String orderBusinessKey = trimToNull(order.orderBusinessKey());
        return orderBusinessKey == null ? order.temporaryOrderCode() : orderBusinessKey;
    }

    /**
     * 从订单快照中提取 Group Code。
     */
    private String groupCode(ReservationAiQueryOrderSnapshot order) {
        if (isHiddenSystemOrder(order)) {
            return null;
        }
        if (ReservationOrderKeyType.GROUP_CODE.name().equals(order.orderKeyType())) {
            return displayOrderKey(order);
        }
        return null;
    }

    /**
     * 从订单快照中提取 Confirmation No.。
     */
    private String confirmationNumber(ReservationAiQueryOrderSnapshot order) {
        if (isHiddenSystemOrder(order)) {
            return null;
        }
        if (ReservationOrderKeyType.CONFIRMATION_NUMBER.name().equals(order.orderKeyType())) {
            return displayOrderKey(order);
        }
        return null;
    }

    /**
     * 判断订单是否为系统隐藏技术订单。该类订单只支撑特殊任务挂靠，不进入普通订单体验。
     */
    private boolean isHiddenSystemOrder(ReservationAiQueryOrderSnapshot order) {
        return order != null && ReservationOrderVisibility.HIDDEN_SYSTEM.name().equals(order.orderVisibility());
    }

    /**
     * 标准化酒店 ID。前端可不传酒店，后端按当前用户上下文或单酒店系统上下文解析。
     */
    private String normalizeHotelId(String hotelId) {
        try {
            return hotelContextService.resolveCurrentHotelId(hotelId);
        } catch (HotelContextException exception) {
            throw new ReservationTaskWorkflowException(
                    exception.getStatus(),
                    exception.getErrorCode(),
                    exception.getMessage());
        }
    }

    /**
     * 标准化页码，避免非法参数导致全表读取。
     */
    private int normalizePageNum(Integer pageNum) {
        return pageNum == null || pageNum < 1 ? DEFAULT_PAGE_NUM : pageNum;
    }

    /**
     * 标准化页大小，并限制最大页大小。
     */
    private int normalizePageSize(Integer pageSize) {
        if (pageSize == null || pageSize < 1) {
            return DEFAULT_PAGE_SIZE;
        }
        return Math.min(pageSize, MAX_PAGE_SIZE);
    }

    /**
     * 去除空白字符串，空字符串按 null 处理。
     */
    private String trimToNull(String value) {
        if (value == null || value.trim().isEmpty()) {
            return null;
        }
        return value.trim();
    }

    /**
     * 来源邮件会话展示上下文。只包含 Inbox 安全摘要，不包含正文、HTML 或附件 URL。
     */
    private record SourceMessageDisplayContext(
            String subject,
            String senderSummary,
            OffsetDateTime receivedAt,
            String externalConversationId,
            Long conversationMessageCount) {
    }

}
