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

import cn.nianxx.thhotel.platform.hotel.service.HotelContextService;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextException;
import cn.nianxx.thhotel.platform.message.common.enums.SourceMessageMediaType;
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageCommand;
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageMedia;
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageCaptureResult;
import cn.nianxx.thhotel.platform.message.service.SourceMessageCaptureService;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationDemoDataSeedRequest;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationOperaSimulationRequest;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationTaskPayloadMutationRequest;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationDemoDataOrderResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationDemoDataSeedResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationDemoDataSourceMessageResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationDemoDataTaskResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOperaOperationResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskDetailResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskPayloadMutationResult;
import cn.nianxx.thhotel.workflows.reservation.common.result.SuperAgentTaskResultItemResponse;
import cn.nianxx.thhotel.workflows.reservation.common.result.SuperAgentTaskResultResponse;
import cn.nianxx.thhotel.workflows.reservation.service.ReservationAiTaskIntakeService;
import cn.nianxx.thhotel.workflows.reservation.service.ReservationDemoDataService;
import cn.nianxx.thhotel.workflows.reservation.service.ReservationTaskWorkflowService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Reservation 演示数据服务实现。通过真实 SourceMessage、SuperAgent 入站、任务确认和 OPERA 模拟服务造数。
 */
@Service
public class ReservationDemoDataServiceImpl implements ReservationDemoDataService {

    private static final String DEMO_CLIENT_ID = "reservation-demo-data-seed";
    private static final String SOURCE_PROVIDER = "AGENTBUS";
    private static final String SOURCE_CHANNEL = "EMAIL";
    private static final String SOURCE_SCHEMA_VERSION = "agentbus-outlook-v1";
    private static final String SCENARIO_QUEUE = "QUEUE_BLOCKED";
    private static final String SCENARIO_QUEUE_REPLY = "QUEUE_CONVERSATION_REPLY";
    private static final String SCENARIO_QUEUE_FIRST = "QUEUE_FIRST";
    private static final String SCENARIO_COMPLETED = "OPERA_COMPLETED";
    private static final String SCENARIO_FAILED = "OPERA_FAILED";
    private static final String SCENARIO_FALLBACK = "FALLBACK_REVIEW";
    private static final String SCENARIO_INFORMATIONAL = "MESSAGE_NOTIFICATION";
    private static final DateTimeFormatter RUN_ID_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    private final ReservationDemoDataProperties properties;
    private final ObjectMapper objectMapper;
    private final SourceMessageCaptureService captureService;
    private final ReservationAiTaskIntakeService intakeService;
    private final ReservationTaskWorkflowService taskWorkflowService;
    private final HotelContextService hotelContextService;

    /**
     * 注入演示配置、JSON 工具和已有业务服务，避免 seed 功能直接写表。
     */
    public ReservationDemoDataServiceImpl(
            ReservationDemoDataProperties properties,
            ObjectMapper objectMapper,
            SourceMessageCaptureService captureService,
            ReservationAiTaskIntakeService intakeService,
            ReservationTaskWorkflowService taskWorkflowService,
            HotelContextService hotelContextService) {
        this.properties = properties;
        this.objectMapper = objectMapper;
        this.captureService = captureService;
        this.intakeService = intakeService;
        this.taskWorkflowService = taskWorkflowService;
        this.hotelContextService = hotelContextService;
    }

    /**
     * 生成前端 P0 页面可直接查看的一批演示数据。该方法默认事务回滚失败的半成品。
     */
    @Override
    @Transactional
    public ReservationDemoDataSeedResult seed(ReservationDemoDataSeedRequest request, String accessKey) {
        ensureAuthorized(accessKey);
        String hotelId = normalizeHotelId(request == null ? null : request.hotelId());
        String demoRunId = buildDemoRunId(request == null ? null : request.runLabel());
        SeedAccumulator accumulator = new SeedAccumulator();

        seedQueueBlockedScenario(hotelId, demoRunId, accumulator);
        seedCompletedOperaScenario(hotelId, demoRunId, accumulator);
        seedFailedOperaScenario(hotelId, demoRunId, accumulator);
        seedFallbackScenario(hotelId, demoRunId, accumulator);
        seedInformationalScenario(hotelId, demoRunId, accumulator);

        return new ReservationDemoDataSeedResult(
                demoRunId,
                hotelId,
                List.copyOf(accumulator.sourceMessages()),
                List.copyOf(accumulator.orders()),
                List.copyOf(accumulator.tasks()),
                entrypoints(hotelId, demoRunId, accumulator),
                notes());
    }

    /**
     * 生成同订单两条任务，第一条未完成，第二条用于前端验证只读阻塞状态。
     */
    private void seedQueueBlockedScenario(String hotelId, String demoRunId, SeedAccumulator accumulator) {
        String conversationId = code("thread-demo-queue", demoRunId);
        String externalMessageId = code("demo-queue-main", demoRunId);
        CapturedSourceMessage mainSource = captureSourceMessage(
                hotelId,
                externalMessageId,
                conversationId,
                "Demo " + demoRunId + " queue booking request",
                "Please create group booking and then update the stay dates. Demo run " + demoRunId + ".",
                "<html><body><p>Please create group booking and then update the stay dates.</p>"
                        + "<img src=\"cid:demo-inline\" /></body></html>",
                Instant.now().minusSeconds(900),
                List.of(
                        new CaptureSourceMessageMedia(
                                SourceMessageMediaType.ATTACHMENT.code(),
                                "demo-rooming-list-" + demoRunId + ".xlsx",
                                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                                2048L,
                                "https://example.test/demo/" + demoRunId + "/rooming-list.xlsx",
                                code("att-rooming", demoRunId)),
                        new CaptureSourceMessageMedia(
                                SourceMessageMediaType.INLINE_IMAGE.code(),
                                "demo-inline-" + demoRunId + ".png",
                                "image/png",
                                1024L,
                                "https://example.test/demo/" + demoRunId + "/inline.png",
                                code("img-inline", demoRunId))));
        CapturedSourceMessage replySource = captureSourceMessage(
                hotelId,
                code("demo-queue-reply", demoRunId),
                conversationId,
                "Re: Demo " + demoRunId + " queue booking request",
                "Adding a note to the same conversation for demo run " + demoRunId + ".",
                "<html><body><p>Adding a note to the same conversation.</p></body></html>",
                Instant.now().minusSeconds(600),
                List.of());

        String groupCode = code("GRP-DEMO-QUEUE", demoRunId).toUpperCase(Locale.ROOT);
        SuperAgentTaskResultResponse response = intake(
                twoTaskBody(hotelId, mainSource.externalMessageId(), demoRunId, groupCode),
                demoRunId,
                SCENARIO_QUEUE);
        SuperAgentTaskResultItemResponse first = response.items().get(0);
        SuperAgentTaskResultItemResponse blocked = response.items().get(1);

        accumulator.sourceMessages().add(sourceMessageResult(SCENARIO_QUEUE, mainSource));
        accumulator.sourceMessages().add(sourceMessageResult(SCENARIO_QUEUE_REPLY, replySource));
        accumulator.orders().add(orderResult(SCENARIO_QUEUE, first, groupCode));
        accumulator.tasks().add(taskResult(SCENARIO_QUEUE_FIRST, first, "new_group_block"));
        accumulator.tasks().add(taskResult(SCENARIO_QUEUE, blocked, "update_stay_dates"));
    }

    /**
     * 生成已确认并两条 OPERA 模拟操作全部成功的任务，用于前端查看 completed 状态。
     */
    private void seedCompletedOperaScenario(String hotelId, String demoRunId, SeedAccumulator accumulator) {
        CapturedSourceMessage source = captureSimpleSourceMessage(
                hotelId,
                SCENARIO_COMPLETED.toLowerCase(Locale.ROOT),
                demoRunId,
                "Demo " + demoRunId + " completed booking request");
        String confirmationNumber = code("CNF-DEMO-DONE", demoRunId).toUpperCase(Locale.ROOT);
        SuperAgentTaskResultResponse response = intake(
                newBookingBody(hotelId, source.externalMessageId(), demoRunId, confirmationNumber),
                demoRunId,
                SCENARIO_COMPLETED);
        SuperAgentTaskResultItemResponse item = response.items().get(0);
        ReservationTaskPayloadMutationResult confirmed = confirmTask(item.taskId());
        executeOperation(item.taskId(), confirmed.operaOperations().get(0).operationId(), true);
        executeOperation(item.taskId(), confirmed.operaOperations().get(1).operationId(), true);
        ReservationTaskDetailResult detail = taskWorkflowService.getTaskDetail(Long.valueOf(item.taskId()));

        accumulator.sourceMessages().add(sourceMessageResult(SCENARIO_COMPLETED, source));
        accumulator.orders().add(orderResult(SCENARIO_COMPLETED, item, confirmationNumber));
        accumulator.tasks().add(taskResult(SCENARIO_COMPLETED, item, "new_fit_reservation", detail.taskStatus()));
    }

    /**
     * 生成已确认但第一条 OPERA 模拟失败的任务，用于前端查看失败和重试入口。
     */
    private void seedFailedOperaScenario(String hotelId, String demoRunId, SeedAccumulator accumulator) {
        CapturedSourceMessage source = captureSimpleSourceMessage(
                hotelId,
                SCENARIO_FAILED.toLowerCase(Locale.ROOT),
                demoRunId,
                "Demo " + demoRunId + " failed operation booking request");
        String confirmationNumber = code("CNF-DEMO-FAIL", demoRunId).toUpperCase(Locale.ROOT);
        SuperAgentTaskResultResponse response = intake(
                newBookingBody(hotelId, source.externalMessageId(), demoRunId, confirmationNumber),
                demoRunId,
                SCENARIO_FAILED);
        SuperAgentTaskResultItemResponse item = response.items().get(0);
        ReservationTaskPayloadMutationResult confirmed = confirmTask(item.taskId());
        executeOperation(item.taskId(), confirmed.operaOperations().get(0).operationId(), false);
        ReservationTaskDetailResult detail = taskWorkflowService.getTaskDetail(Long.valueOf(item.taskId()));

        accumulator.sourceMessages().add(sourceMessageResult(SCENARIO_FAILED, source));
        accumulator.orders().add(orderResult(SCENARIO_FAILED, item, confirmationNumber));
        accumulator.tasks().add(taskResult(SCENARIO_FAILED, item, "new_fit_reservation", detail.taskStatus()));
    }

    /**
     * 生成 Fallback 人工复核任务，用于前端查看异常任务转换前状态。
     */
    private void seedFallbackScenario(String hotelId, String demoRunId, SeedAccumulator accumulator) {
        CapturedSourceMessage source = captureSimpleSourceMessage(
                hotelId,
                SCENARIO_FALLBACK.toLowerCase(Locale.ROOT),
                demoRunId,
                "Demo " + demoRunId + " fallback review request");
        SuperAgentTaskResultResponse response = intake(
                fallbackBody(hotelId, source.externalMessageId(), demoRunId),
                demoRunId,
                SCENARIO_FALLBACK);
        SuperAgentTaskResultItemResponse item = response.items().get(0);

        accumulator.sourceMessages().add(sourceMessageResult(SCENARIO_FALLBACK, source));
        accumulator.orders().add(orderResult(SCENARIO_FALLBACK, item, temporaryOrderCode(source)));
        accumulator.tasks().add(taskResult(SCENARIO_FALLBACK, item, "manual_review"));
    }

    /**
     * 生成 Message Notification 任务，用于前端验证非执行队列消息提醒。
     */
    private void seedInformationalScenario(String hotelId, String demoRunId, SeedAccumulator accumulator) {
        CapturedSourceMessage source = captureSimpleSourceMessage(
                hotelId,
                SCENARIO_INFORMATIONAL.toLowerCase(Locale.ROOT),
                demoRunId,
                "Demo " + demoRunId + " informational reply");
        SuperAgentTaskResultResponse response = intake(
                informationalBody(hotelId, source.externalMessageId(), demoRunId),
                demoRunId,
                SCENARIO_INFORMATIONAL);
        SuperAgentTaskResultItemResponse item = response.items().get(0);

        accumulator.sourceMessages().add(sourceMessageResult(SCENARIO_INFORMATIONAL, source));
        accumulator.orders().add(orderResult(SCENARIO_INFORMATIONAL, item, temporaryOrderCode(source)));
        accumulator.tasks().add(taskResult(SCENARIO_INFORMATIONAL, item, "thank_you"));
    }

    /**
     * 校验 seed 开关和访问口令，口令比较使用常量时间比较，避免明显时序差异。
     */
    private void ensureAuthorized(String accessKey) {
        if (!properties.isEnabled()) {
            throw error(HttpStatus.SERVICE_UNAVAILABLE, "DEMO_DATA_DISABLED", "演示数据 seed 接口未启用。");
        }
        String configuredKey = trimToNull(properties.getAccessKey());
        if (configuredKey == null) {
            throw error(HttpStatus.SERVICE_UNAVAILABLE, "DEMO_DATA_ACCESS_KEY_NOT_CONFIGURED", "演示数据访问口令未配置。");
        }
        if (!accessKeyMatches(configuredKey, accessKey)) {
            throw error(HttpStatus.FORBIDDEN, "DEMO_DATA_ACCESS_DENIED", "演示数据访问被拒绝。");
        }
    }

    /**
     * 生成同订单两任务的 SuperAgent 入站请求体。
     */
    private String twoTaskBody(String hotelId, String externalMessageId, String demoRunId, String groupCode) {
        return taskResultBody(hotelId, externalMessageId, List.of(
                taskItem(
                        1,
                        "S01",
                        "S01_new_booking_skill",
                        "normal_task",
                        "New Booking",
                        "new_group_block",
                        "AI extracted demo new group booking " + demoRunId + ".",
                        "Please create the group booking for " + demoRunId + ".",
                        Map.of("group_code", groupCode),
                        Map.of(
                                "booking_object_type", "Group Block",
                                "arrival_date", "2026-08-01",
                                "departure_date", "2026-08-04",
                                "room_quantity", 2,
                                "room_type", "Deluxe Twin",
                                "pms_room_type_code", "RM2",
                                "rate_code_result", Map.of(
                                        "rate_code", "GRPA1-900",
                                        "settlement_price", 900),
                                "fix_charge_required", false),
                        Map.of("demo_run_id", demoRunId)),
                taskItem(
                        2,
                        "S02",
                        "S02_update_booking_amendment_skill",
                        "normal_task",
                        "Update Booking",
                        "update_stay_dates",
                        "AI extracted demo update booking " + demoRunId + ".",
                        "Please update the group booking for " + demoRunId + ".",
                        Map.of("group_code", groupCode),
                        Map.of(
                                "update_subtypes", List.of("update_stay_dates"),
                                "before_after", Map.of(
                                        "arrival_date", Map.of("before", "2026-08-01", "after", "2026-08-02"),
                                        "departure_date", Map.of("before", "2026-08-04", "after", "2026-08-05"))),
                        Map.of("demo_run_id", demoRunId))));
    }

    /**
     * 生成 New Booking 单任务 SuperAgent 入站请求体。
     */
    private String newBookingBody(
            String hotelId,
            String externalMessageId,
            String demoRunId,
            String confirmationNumber) {
        return taskResultBody(hotelId, externalMessageId, List.of(taskItem(
                1,
                "S01",
                "S01_new_booking_skill",
                "normal_task",
                "New Booking",
                "new_fit_reservation",
                "AI extracted demo FIT booking " + demoRunId + ".",
                "Please create the FIT booking for " + demoRunId + ".",
                Map.of("confirmation_number", confirmationNumber),
                Map.of(
                        "booking_object_type", "FIT Reservation",
                        "arrival_date", "2026-08-10",
                        "departure_date", "2026-08-12",
                        "room_quantity", 1,
                        "room_type", "Deluxe King",
                        "pms_room_type_code", "RM2",
                        "rate_code_result", Map.of(
                                "rate_code", "GRPA1-900",
                                "settlement_price", 900),
                        "fix_charge_required", false),
                Map.of("demo_run_id", demoRunId))));
    }

    /**
     * 生成 Fallback 人工复核 SuperAgent 入站请求体。
     */
    private String fallbackBody(String hotelId, String externalMessageId, String demoRunId) {
        Map<String, Object> item = taskItem(
                1,
                "S99",
                "S99_fallback_manual_review_skill",
                "manual_review",
                "Fallback",
                "manual_review",
                "AI could not classify demo message " + demoRunId + ".",
                "Please review ambiguous booking message for " + demoRunId + ".",
                Map.of("confirmation_number", code("CNF-DEMO-FB", demoRunId).toUpperCase(Locale.ROOT)),
                Map.of(
                        "booking_object_type", "FIT Reservation",
                        "arrival_date", "2026-09-01",
                        "departure_date", "2026-09-03"),
                Map.of("demo_run_id", demoRunId));
        item.put("manual_review", Map.of(
                "reason_code", "ambiguous_task_type",
                "reason_text", "Demo fallback needs human classification.",
                "candidate_task_types", List.of("New Booking", "Update Booking", "Cancel Booking")));
        return taskResultBody(hotelId, externalMessageId, List.of(item));
    }

    /**
     * 生成 Message Notification SuperAgent 入站请求体。
     */
    private String informationalBody(String hotelId, String externalMessageId, String demoRunId) {
        Map<String, Object> item = taskItem(
                1,
                "S98",
                "S98_message_notification_skill",
                "informational_message",
                "Message Notification",
                "thank_you",
                "AI extracted demo informational message " + demoRunId + ".",
                "Guest replied thanks for " + demoRunId + ".",
                Map.of(),
                Map.of(),
                Map.of("demo_run_id", demoRunId));
        item.put("informational_message", Map.of(
                "category", "guest_reply",
                "summary", "Guest acknowledged the reservation update.",
                "severity", "low"));
        return taskResultBody(hotelId, externalMessageId, List.of(item));
    }

    /**
     * 组装 SuperAgent 顶层请求体。
     */
    private String taskResultBody(String hotelId, String externalMessageId, List<Map<String, Object>> items) {
        Map<String, Object> root = new LinkedHashMap<>();
        root.put("hotel_id", hotelId);
        root.put("source_message_id", externalMessageId);
        root.put("ai_task_results", items);
        root.put("extraction_warnings", List.of());
        return toJson(root);
    }

    /**
     * 组装单条 ai_task_results item，保留 SuperAgent 已稳定字段。
     */
    private Map<String, Object> taskItem(
            int sourceEventIndex,
            String catalogCode,
            String skillId,
            String resultType,
            String taskType,
            String taskSubtype,
            String visibleReason,
            String relevantMessageExcerpt,
            Map<String, Object> caseKeys,
            Map<String, Object> extractedFields,
            Map<String, Object> contextUsed) {
        Map<String, Object> item = new LinkedHashMap<>();
        item.put("source_event_index", sourceEventIndex);
        item.put("catalog_code", catalogCode);
        item.put("skill_id", skillId);
        item.put("result_type", resultType);
        item.put("task_type", taskType);
        item.put("task_subtype", taskSubtype);
        item.put("current_or_history", "current");
        item.put("visible_reason", visibleReason);
        item.put("relevant_message_excerpt", relevantMessageExcerpt);
        item.put("attachments", List.of());
        item.put("file_references", List.of());
        item.put("context_used", contextUsed);
        item.put("case_keys", caseKeys);
        item.put("extracted_fields", extractedFields);
        item.put("additional_operations", List.of());
        item.put("idempotency_key", null);
        return item;
    }

    /**
     * 捕获一封简单演示邮件。
     */
    private CapturedSourceMessage captureSimpleSourceMessage(
            String hotelId,
            String scenarioCode,
            String demoRunId,
            String subject) {
        return captureSourceMessage(
                hotelId,
                code("demo-" + scenarioCode, demoRunId),
                code("thread-demo-" + scenarioCode, demoRunId),
                subject,
                "Demo mail for " + demoRunId + " scenario " + scenarioCode + ".",
                "<html><body><p>Demo mail for " + demoRunId + " scenario " + scenarioCode + ".</p></body></html>",
                Instant.now().minusSeconds(300),
                List.of());
    }

    /**
     * 调用 SourceMessage 捕获服务落库演示邮件。
     */
    private CapturedSourceMessage captureSourceMessage(
            String hotelId,
            String externalMessageId,
            String externalConversationId,
            String subject,
            String textBody,
            String htmlBody,
            Instant sourceSentAt,
            List<CaptureSourceMessageMedia> mediaItems) {
        SourceMessageCaptureResult result = captureService.capture(new CaptureSourceMessageCommand(
                hotelId,
                SOURCE_PROVIDER,
                SOURCE_CHANNEL,
                externalMessageId,
                externalConversationId,
                code("frame", externalMessageId),
                code("session-demo-data", hotelId),
                sourceSentAt,
                "demo.guest@example.test",
                subject,
                textBody,
                htmlBody,
                toJson(Map.of(
                        "demo_run_id", externalMessageId,
                        "source", Map.of("external_message_id", externalMessageId))),
                SOURCE_SCHEMA_VERSION,
                mediaItems));
        if (!"RECEIVED".equals(result.captureStatus())) {
            throw error(HttpStatus.CONFLICT, "DEMO_SOURCE_MESSAGE_CAPTURE_FAILED", "演示来源消息入库失败。");
        }
        return new CapturedSourceMessage(result.inboxId(), externalMessageId, externalConversationId, subject);
    }

    /**
     * 调用 AI 入站服务创建订单、任务和任务卡。
     */
    private SuperAgentTaskResultResponse intake(String rawBody, String demoRunId, String scenarioCode) {
        SuperAgentTaskResultResponse response = intakeService.accept(
                rawBody,
                DEMO_CLIENT_ID,
                "demo-data-" + scenarioCode.toLowerCase(Locale.ROOT) + "-" + demoRunId);
        if (response.items().isEmpty()) {
            throw error(HttpStatus.CONFLICT, "DEMO_INTAKE_EMPTY", "演示 AI 入站未创建任务。");
        }
        return response;
    }

    /**
     * 确认任务并生成固定两条 OPERA 模拟操作。
     */
    private ReservationTaskPayloadMutationResult confirmTask(String taskId) {
        return taskWorkflowService.confirmTask(
                Long.valueOf(taskId),
                new ReservationTaskPayloadMutationRequest(Map.of()));
    }

    /**
     * 执行一条 OPERA 模拟操作，成功或失败由调用方指定。
     */
    private ReservationOperaOperationResult executeOperation(String taskId, String operationId, boolean success) {
        return taskWorkflowService.executeOperaOperation(
                Long.valueOf(taskId),
                Long.valueOf(operationId),
                new ReservationOperaSimulationRequest(success, success ? null : "Demo OPERA failure for retry."));
    }

    /**
     * 转换来源消息为 seed 响应对象。
     */
    private ReservationDemoDataSourceMessageResult sourceMessageResult(
            String scenarioCode,
            CapturedSourceMessage source) {
        return new ReservationDemoDataSourceMessageResult(
                scenarioCode,
                source.sourceMessageId().toString(),
                source.externalMessageId(),
                source.externalConversationId(),
                source.subject());
    }

    /**
     * 转换订单为 seed 响应对象。
     */
    private ReservationDemoDataOrderResult orderResult(
            String scenarioCode,
            SuperAgentTaskResultItemResponse item,
            String displayOrderKey) {
        return new ReservationDemoDataOrderResult(
                scenarioCode,
                item.orderId(),
                item.orderStatus(),
                displayOrderKey);
    }

    /**
     * 转换任务为 seed 响应对象，使用 AI 入站后的初始状态。
     */
    private ReservationDemoDataTaskResult taskResult(
            String scenarioCode,
            SuperAgentTaskResultItemResponse item,
            String taskSubtype) {
        return taskResult(scenarioCode, item, taskSubtype, item.taskStatus());
    }

    /**
     * 转换任务为 seed 响应对象，允许覆盖任务后续状态。
     */
    private ReservationDemoDataTaskResult taskResult(
            String scenarioCode,
            SuperAgentTaskResultItemResponse item,
            String taskSubtype,
            String taskStatus) {
        return new ReservationDemoDataTaskResult(
                scenarioCode,
                item.taskId(),
                item.orderId(),
                item.systemTaskType(),
                taskSubtype,
                taskStatus);
    }

    /**
     * 生成前端可直接调用的查询入口。
     */
    private Map<String, String> entrypoints(String hotelId, String demoRunId, SeedAccumulator accumulator) {
        String encodedRunId = urlEncode(demoRunId);
        String queueOrderId = accumulator.orders().stream()
                .filter(order -> SCENARIO_QUEUE.equals(order.scenarioCode()))
                .map(ReservationDemoDataOrderResult::orderId)
                .findFirst()
                .orElse("");
        String queueSourceMessageId = accumulator.sourceMessages().stream()
                .filter(source -> SCENARIO_QUEUE.equals(source.scenarioCode()))
                .map(ReservationDemoDataSourceMessageResult::sourceMessageId)
                .findFirst()
                .orElse("");
        String failedTaskId = accumulator.tasks().stream()
                .filter(task -> SCENARIO_FAILED.equals(task.scenarioCode()))
                .map(ReservationDemoDataTaskResult::taskId)
                .findFirst()
                .orElse("");
        Map<String, String> entrypoints = new LinkedHashMap<>();
        entrypoints.put("task_list_url", "/api/reservation/tasks?keyword=" + encodedRunId
                + "&page_num=1&page_size=20");
        entrypoints.put("order_list_url", "/api/reservation/orders?keyword=" + encodedRunId
                + "&page_num=1&page_size=20");
        entrypoints.put("queue_order_detail_url", "/api/reservation/orders/" + queueOrderId
                + "?include_tasks=true&include_source_summary=true");
        entrypoints.put("failed_task_detail_url", "/api/reservation/tasks/" + failedTaskId);
        entrypoints.put("source_conversation_url", "/api/source-messages/" + queueSourceMessageId + "/conversation");
        return entrypoints;
    }

    /**
     * 返回演示数据使用注意事项。
     */
    private List<String> notes() {
        return List.of(
                "该接口默认关闭，仅用于 dev/test 前端联调造数。",
                "演示数据通过真实业务服务写入，可用于任务列表、订单列表、订单详情、任务详情和邮件会话详情。",
                "OPERA 仍为模拟骨架，失败场景只用于验证前端重试入口。");
    }

    /**
     * 生成安全短 run id，用于业务号、邮件主题和 keyword 查询。
     */
    private String buildDemoRunId(String rawRunLabel) {
        String label = sanitizeCode(trimToNull(rawRunLabel) == null ? "demo" : rawRunLabel);
        String time = LocalDateTime.now(ZoneOffset.UTC).format(RUN_ID_TIME_FORMATTER);
        String suffix = UUID.randomUUID().toString().substring(0, 8);
        return label + "-" + time + "-" + suffix;
    }

    /**
     * 标准化酒店上下文 ID。
     */
    private String normalizeHotelId(String rawHotelId) {
        try {
            return hotelContextService.resolveCurrentHotelId(rawHotelId);
        } catch (HotelContextException exception) {
            throw new ReservationTaskWorkflowException(
                    exception.getStatus(),
                    exception.getErrorCode(),
                    exception.getMessage());
        }
    }

    /**
     * 拼接稳定代码，保持长度适合外部消息 ID 和业务号字段。
     */
    private String code(String prefix, String suffix) {
        return prefix + "-" + sanitizeCode(suffix);
    }

    /**
     * 仅保留适合业务号和消息 ID 的字符，并限制长度。
     */
    private String sanitizeCode(String value) {
        String sanitized = value == null
                ? "demo"
                : value.trim().replaceAll("[^A-Za-z0-9_-]", "-").replaceAll("-+", "-");
        if (sanitized.isBlank()) {
            return "demo";
        }
        return sanitized.length() > 48 ? sanitized.substring(0, 48) : sanitized;
    }

    /**
     * 访问口令常量时间比较。
     */
    private boolean accessKeyMatches(String configuredKey, String providedKey) {
        if (providedKey == null) {
            return false;
        }
        return MessageDigest.isEqual(
                sha256Bytes(configuredKey),
                sha256Bytes(providedKey));
    }

    /**
     * 计算访问口令摘要，用固定长度字节数组做常量时间比较。
     */
    private byte[] sha256Bytes(String value) {
        try {
            return MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException exception) {
            throw new IllegalStateException("当前 Java 运行时不支持 SHA-256", exception);
        }
    }

    /**
     * 生成与订单创建逻辑一致的临时订单展示号。
     */
    private String temporaryOrderCode(CapturedSourceMessage source) {
        return "TMP-SM-" + source.sourceMessageId() + "-1";
    }

    /**
     * JSON 序列化工具，避免手工拼接 SuperAgent 请求体。
     */
    private String toJson(Object value) {
        try {
            return objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException exception) {
            throw new IllegalStateException("演示数据 JSON 构建失败。", exception);
        }
    }

    /**
     * URL 参数编码。
     */
    private String urlEncode(String value) {
        return URLEncoder.encode(value, StandardCharsets.UTF_8);
    }

    /**
     * 清理空白字符串。
     */
    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    /**
     * 构建受控业务异常。
     */
    private ReservationTaskWorkflowException error(HttpStatus status, String errorCode, String message) {
        return new ReservationTaskWorkflowException(status, errorCode, message);
    }

    /**
     * seed 过程中的来源消息引用。
     */
    private record CapturedSourceMessage(
            Long sourceMessageId,
            String externalMessageId,
            String externalConversationId,
            String subject
    ) {
    }

    /**
     * seed 过程中的响应累加器，保持返回顺序稳定。
     */
    private record SeedAccumulator(
            List<ReservationDemoDataSourceMessageResult> sourceMessages,
            List<ReservationDemoDataOrderResult> orders,
            List<ReservationDemoDataTaskResult> tasks
    ) {

        private SeedAccumulator() {
            this(new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
        }
    }
}
