package cn.nianxx.thhotel.integrations.mcp.superagent.control;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import cn.nianxx.thhotel.ThHotelApplication;
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageCommand;
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageCaptureResult;
import cn.nianxx.thhotel.platform.message.service.SourceMessageCaptureService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.time.Instant;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

@SpringBootTest(
        classes = ThHotelApplication.class,
        properties = {
                "mcp.enabled=true",
                "mcp.auth-token=test-mcp-token",
                "mcp.enable-submit-task-results=true",
                "mcp.max-body-bytes=12000"
        })
@AutoConfigureMockMvc
@ActiveProfiles("test")
class SuperAgentMcpSubmitEnabledControllerTest {

    private static final String ENDPOINT = "/mcp";
    private static final String AUTHORIZATION = "Bearer test-mcp-token";

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private SourceMessageCaptureService captureService;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void shouldDelegateSubmitTaskResultsToolWhenWriteToolEnabled() throws Exception {
        String body = """
                {
                  "jsonrpc": "2.0",
                  "id": "mcp-submit-enabled-001",
                  "method": "tools/call",
                  "params": {
                    "name": "th_hotel_submit_task_results",
                    "arguments": {
                      "source_message_id": "mail-mcp-enabled-missing-001",
                      "ai_task_results": [
                        {
                          "source_event_index": 1,
                          "catalog_code": "S01",
                          "skill_id": "S01_new_booking_skill",
                          "result_type": "normal_task",
                          "task_type": "NEW_BOOKING"
                        }
                      ]
                    }
                  }
                }
                """;

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(body))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.jsonrpc").value("2.0"))
                .andExpect(jsonPath("$.id").value("mcp-submit-enabled-001"))
                .andExpect(jsonPath("$.result.isError").value(true))
                .andExpect(jsonPath("$.result.structuredContent.error.code").value("SOURCE_MESSAGE_NOT_FOUND"))
                .andExpect(content().string(not(containsString("MCP_TOOL_DISABLED"))));
    }

    @Test
    void shouldRejectUnknownV3RootFieldBeforeDelegatingSubmit() throws Exception {
        String externalId = "mail-mcp-v3-unknown-root-001";
        SourceMessageCaptureResult source = captureSourceMessage(externalId);
        String businessRoot = parentSplitBusinessRoot(externalId)
                .replace("\"unhandled_current_intents\": []", "\"unhandled_current_intents\": [], \"unexpected_root\": true");

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-v3-unknown-root-001", businessRoot)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(true))
                .andExpect(jsonPath("$.result.structuredContent.error.code").value("MCP_SUBMIT_PAYLOAD_INVALID"))
                .andExpect(jsonPath("$.result.structuredContent.error.details.field").value("unexpected_root"));

        assertNoReservationWorkflowRows(source.inboxId());
    }

    @Test
    void shouldKeepInfrastructureErrorWhenV3SourceMessageIdMissing() throws Exception {
        String businessRoot = parentSplitBusinessRootWithoutSourceMessageId("mail-mcp-v3-missing-source-id-001");

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-v3-missing-source-id-001", businessRoot)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(true))
                .andExpect(jsonPath("$.result.structuredContent.error.code").value("MISSING_SOURCE_MESSAGE_ID"))
                .andExpect(jsonPath("$.result.structuredContent.error.details.http_status").value(400));
    }

    @Test
    void shouldKeepInfrastructureErrorWhenV3SourceMessageMissing() throws Exception {
        String businessRoot = parentSplitBusinessRootWithoutSourceMessage("mail-mcp-v3-missing-source-object-001");

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-v3-missing-source-object-001", businessRoot)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(true))
                .andExpect(jsonPath("$.result.structuredContent.error.code").value("MISSING_SOURCE_MESSAGE_ID"))
                .andExpect(jsonPath("$.result.structuredContent.error.details.http_status").value(400));
    }

    @Test
    void shouldRejectIncompleteV2ItemBeforeDelegatingSubmit() throws Exception {
        String externalId = "mail-mcp-v2-incomplete-item-001";
        SourceMessageCaptureResult source = captureSourceMessage(externalId);
        String body = """
                {
                  "jsonrpc": "2.0",
                  "id": "mcp-submit-v2-incomplete-item-001",
                  "method": "tools/call",
                  "params": {
                    "name": "th_hotel_submit_task_results",
                    "arguments": {
                      "source_message_id": "%s",
                      "ai_task_results": [
                        {
                          "source_event_index": 1,
                          "catalog_code": "S01",
                          "skill_id": "S01_new_booking_skill",
                          "result_type": "normal_task"
                        }
                      ]
                    }
                  }
                }
                """.formatted(externalId);

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(body))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(true))
                .andExpect(jsonPath("$.result.structuredContent.error.code").value("MCP_SUBMIT_PAYLOAD_INVALID"))
                .andExpect(jsonPath("$.result.structuredContent.error.details.field").value("ai_task_results[].task_type"));

        assertNoReservationWorkflowRows(source.inboxId());
    }

    @Test
    void shouldRejectDanglingRelatedEventIndexBeforeDelegatingSubmit() throws Exception {
        String externalId = "mail-mcp-v3-dangling-related-001";
        SourceMessageCaptureResult source = captureSourceMessage(externalId);
        String businessRoot = parentSplitBusinessRootWithSecondParentRelation(externalId, "E_UNKNOWN_CHILD");

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-v3-dangling-related-001", businessRoot)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(true))
                .andExpect(jsonPath("$.result.structuredContent.error.code").value("MCP_SUBMIT_PAYLOAD_INVALID"))
                .andExpect(jsonPath("$.result.structuredContent.error.details.field").value("related_source_event_indices"));

        assertNoReservationWorkflowRows(source.inboxId());
    }

    @Test
    void shouldRejectDuplicateRelatedEventIndexBeforeDelegatingSubmit() throws Exception {
        String externalId = "mail-mcp-v3-duplicate-related-001";
        SourceMessageCaptureResult source = captureSourceMessage(externalId);
        String businessRoot = parentSplitBusinessRootWithSecondParentRelation(externalId, "E_CHILD_1");

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-v3-duplicate-related-001", businessRoot)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(true))
                .andExpect(jsonPath("$.result.structuredContent.error.code").value("MCP_SUBMIT_PAYLOAD_INVALID"))
                .andExpect(jsonPath("$.result.structuredContent.error.details.field").value("related_source_event_indices"));

        assertNoReservationWorkflowRows(source.inboxId());
    }

    @Test
    void shouldMapBusinessEventIdsToMcpIndicesAndSubmitOnce() throws Exception {
        String externalId = "mail-mcp-v3-event-map-001";
        SourceMessageCaptureResult source = captureSourceMessage(externalId);

        MvcResult result = mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-v3-event-map-001", parentSplitBusinessRoot(externalId))))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(false))
                .andExpect(jsonPath("$.result.structuredContent.accepted_count").value(3))
                .andExpect(jsonPath("$.result.structuredContent.items[0].source_event_index").value(1))
                .andExpect(jsonPath("$.result.structuredContent.items[1].source_event_index").value(2))
                .andExpect(jsonPath("$.result.structuredContent.items[2].source_event_index").value(3))
                .andExpect(jsonPath("$.result.structuredContent.mapping_diagnostics.source_event_index_mapping[0].original_source_event_index")
                        .value("E_CHILD_1"))
                .andExpect(jsonPath("$.result.structuredContent.mapping_diagnostics.source_event_index_mapping[0].mapped_source_event_index")
                        .value(1))
                .andExpect(jsonPath("$.result.structuredContent.mapping_diagnostics.source_event_index_mapping[2].original_source_event_index")
                        .value("E_PARENT"))
                .andExpect(jsonPath("$.result.structuredContent.mapping_diagnostics.source_event_index_mapping[2].mapped_source_event_index")
                        .value(3))
                .andReturn();

        Long batchCount = jdbcTemplate.queryForObject("""
                SELECT COUNT(*)
                FROM workflow_reservation_ai_batch
                WHERE source_message_id = ?
                """, Long.class, source.inboxId());
        assertThat(batchCount).isEqualTo(1L);

        String parentPayloadJson = jdbcTemplate.queryForObject("""
                SELECT ai_payload_json
                FROM workflow_reservation_ai_transition
                WHERE source_message_id = ?
                  AND source_event_index = 3
                """, String.class, source.inboxId());
        JsonNode parentPayload = objectMapper.readTree(parentPayloadJson);
        assertThat(parentPayload.path("v3_message_event").path("source_event_index").asInt()).isEqualTo(3);
        assertThat(parentPayload.path("v3_message_event").path("related_source_event_indices").get(0).asText())
                .isEqualTo("1");
        assertThat(parentPayload.path("v3_message_event").path("related_source_event_indices").get(1).asText())
                .isEqualTo("2");
        assertThat(parentPayload.toString()).doesNotContain("mapping_diagnostics");
        assertThat(result.getResponse().getContentAsString()).contains("E_CHILD_1");
    }

    @Test
    void shouldMapCrossChildTraceRelationsAndSubmitOnce() throws Exception {
        String externalId = "mail-mcp-v3-trace-cross-child-001";
        SourceMessageCaptureResult source = captureSourceMessage(externalId);

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-v3-trace-cross-child-001", traceCrossChildBusinessRoot(externalId))))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(false))
                .andExpect(jsonPath("$.result.structuredContent.accepted_count").value(3))
                .andExpect(jsonPath("$.result.structuredContent.items[2].source_event_index").value(3))
                .andExpect(jsonPath("$.result.structuredContent.mapping_diagnostics.source_event_index_mapping[2].original_source_event_index")
                        .value("E_TRACE"))
                .andExpect(jsonPath("$.result.structuredContent.mapping_diagnostics.source_event_index_mapping[2].mapped_source_event_index")
                        .value(3));

        String tracePayloadJson = jdbcTemplate.queryForObject("""
                SELECT ai_payload_json
                FROM workflow_reservation_ai_transition
                WHERE source_message_id = ?
                  AND source_event_index = 3
                """, String.class, source.inboxId());
        JsonNode tracePayload = objectMapper.readTree(tracePayloadJson);
        assertThat(tracePayload.path("v3_message_event").path("event_type").asText()).isEqualTo("Trace");
        assertThat(tracePayload.path("v3_message_event").path("related_source_event_indices").get(0).asText())
                .isEqualTo("1");
        assertThat(tracePayload.path("v3_message_event").path("related_source_event_indices").get(1).asText())
                .isEqualTo("2");
    }

    @Test
    void shouldAllowRelationshipTypeOnV3EventAndLetBusinessLayerHandleIt() throws Exception {
        String externalId = "mail-mcp-v3-relationship-root-001";
        SourceMessageCaptureResult source = captureSourceMessage(externalId);
        String businessRoot = parentSplitBusinessRootWithRootRelationshipType(externalId);

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-v3-relationship-root-001", businessRoot)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(false))
                .andExpect(jsonPath("$.result.structuredContent.accepted_count").value(3));

        Long transitionCount = jdbcTemplate.queryForObject("""
                SELECT COUNT(*)
                FROM workflow_reservation_ai_transition
                WHERE source_message_id = ?
                """, Long.class, source.inboxId());
        assertThat(transitionCount).isPositive();
    }

    @Test
    void shouldLetBusinessLayerPersistInvalidV3EventAsContractError() throws Exception {
        String externalId = "mail-mcp-v3-business-contract-error-001";
        SourceMessageCaptureResult source = captureSourceMessage(externalId);
        String businessRoot = businessRootWithInvalidEventShape(externalId);

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-v3-business-contract-error-001", businessRoot)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(false))
                .andExpect(jsonPath("$.result.structuredContent.items[0].system_process_category")
                        .value("ADAPTER_CONTRACT_ERROR"))
                .andExpect(jsonPath("$.result.structuredContent.items[0].adapter_error_code")
                        .value("EVENT_CONTRACT_INCOMPLETE"));

        Long transitionCount = jdbcTemplate.queryForObject("""
                SELECT COUNT(*)
                FROM workflow_reservation_ai_transition
                WHERE source_message_id = ?
                  AND result_type = 'adapter_contract_error'
                """, Long.class, source.inboxId());
        assertThat(transitionCount).isEqualTo(1L);
    }

    @Test
    void shouldSubmitS10ThroughMcpAdapter() throws Exception {
        String externalId = "mail-mcp-s10-001";
        captureSourceMessage(externalId);

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-s10-001", s10NotificationRoot(externalId))))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(false))
                .andExpect(jsonPath("$.result.structuredContent.accepted_count").value(1))
                .andExpect(jsonPath("$.result.structuredContent.items[0].system_task_type").value("SOURCE_MESSAGE_ONLY"));
    }

    @Test
    void shouldSubmitS99ThroughMcpAdapter() throws Exception {
        String externalId = "mail-mcp-s99-001";
        captureSourceMessage(externalId);

        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", AUTHORIZATION)
                        .content(toolCall("mcp-submit-s99-001", s99NotificationRoot(externalId))))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.isError").value(false))
                .andExpect(jsonPath("$.result.structuredContent.accepted_count").value(1))
                .andExpect(jsonPath("$.result.structuredContent.items[0].system_task_type").value("SOURCE_MESSAGE_ONLY"));
    }

    private SourceMessageCaptureResult captureSourceMessage(String externalMessageId) {
        return captureService.capture(new CaptureSourceMessageCommand(
                "HOTEL-TEST",
                "AGENTBUS",
                "EMAIL",
                externalMessageId,
                "thread-" + externalMessageId,
                "frame-" + externalMessageId,
                "session-mcp-submit",
                Instant.parse("2026-07-12T04:00:00Z"),
                "agent@example.test",
                "MCP submit fixture",
                "MCP submit fixture source message.",
                "<html><body>MCP submit fixture source message.</body></html>",
                "{\"source\":{\"external_message_id\":\"" + externalMessageId + "\"}}",
                "agentbus-outlook-v1",
                List.of()
        ));
    }

    private void assertNoReservationWorkflowRows(Long sourceMessageId) {
        Long batchCount = jdbcTemplate.queryForObject("""
                SELECT COUNT(*)
                FROM workflow_reservation_ai_batch
                WHERE source_message_id = ?
                """, Long.class, sourceMessageId);
        Long taskCount = jdbcTemplate.queryForObject("""
                SELECT COUNT(*)
                FROM workflow_reservation_task
                WHERE source_message_id = ?
                """, Long.class, sourceMessageId);
        assertThat(batchCount).isZero();
        assertThat(taskCount).isZero();
    }

    private String toolCall(String id, String argumentsJson) {
        return """
                {
                  "jsonrpc": "2.0",
                  "id": "%s",
                  "method": "tools/call",
                  "params": {
                    "name": "th_hotel_submit_task_results",
                    "arguments": %s
                  }
                }
                """.formatted(id, argumentsJson);
    }

    private String parentSplitBusinessRootWithSecondParentRelation(String externalMessageId, String relatedEventIndex)
            throws Exception {
        ObjectNode root = (ObjectNode) objectMapper.readTree(parentSplitBusinessRoot(externalMessageId));
        ObjectNode parentEvent = (ObjectNode) root.path("message_events").get(2);
        ArrayNode relatedIndices = (ArrayNode) parentEvent.path("related_source_event_indices");
        relatedIndices.set(1, objectMapper.getNodeFactory().textNode(relatedEventIndex));
        return objectMapper.writeValueAsString(root);
    }

    private String parentSplitBusinessRootWithoutSourceMessageId(String externalMessageId) throws Exception {
        ObjectNode root = (ObjectNode) objectMapper.readTree(parentSplitBusinessRoot(externalMessageId));
        ((ObjectNode) root.path("source_message")).remove("source_message_id");
        return objectMapper.writeValueAsString(root);
    }

    private String parentSplitBusinessRootWithoutSourceMessage(String externalMessageId) throws Exception {
        ObjectNode root = (ObjectNode) objectMapper.readTree(parentSplitBusinessRoot(externalMessageId));
        root.remove("source_message");
        return objectMapper.writeValueAsString(root);
    }

    private String parentSplitBusinessRootWithRootRelationshipType(String externalMessageId) throws Exception {
        ObjectNode root = (ObjectNode) objectMapper.readTree(parentSplitBusinessRoot(externalMessageId));
        ObjectNode parentEvent = (ObjectNode) root.path("message_events").get(2);
        parentEvent.put("relationship_type", "linked_parent_release_after_child_split");
        return objectMapper.writeValueAsString(root);
    }

    private String businessRootWithInvalidEventShape(String externalMessageId) throws Exception {
        ObjectNode root = (ObjectNode) objectMapper.readTree(parentSplitBusinessRoot(externalMessageId));
        ArrayNode events = objectMapper.createArrayNode();
        ObjectNode invalidEvent = ((ObjectNode) root.path("message_events").get(0)).deepCopy();
        invalidEvent.put("event_type", "Unsupported Event Type");
        events.add(invalidEvent);
        root.set("message_events", events);
        return objectMapper.writeValueAsString(root);
    }

    private String traceCrossChildBusinessRoot(String externalMessageId) throws Exception {
        ObjectNode root = (ObjectNode) objectMapper.readTree(parentSplitBusinessRoot(externalMessageId));
        ArrayNode events = objectMapper.createArrayNode();
        events.add(root.path("message_events").get(0));
        events.add(root.path("message_events").get(1));
        ObjectNode trace = objectMapper.createObjectNode();
        trace.put("event_type", "Trace");
        trace.put("event_role", "travel_agent_request");
        trace.put("source_event_index", "E_TRACE");
        trace.put("current_or_history", "current");
        ObjectNode caseKeys = objectMapper.createObjectNode();
        caseKeys.putNull("group_code");
        caseKeys.putNull("confirmation_number");
        caseKeys.putNull("reservation_number");
        caseKeys.putNull("block_code");
        trace.set("case_keys", caseKeys);
        trace.put("relevant_message_excerpt", "Please add a shared trace to both child groups.");
        trace.set("attachments", objectMapper.createArrayNode());
        trace.set("file_references", objectMapper.createArrayNode());
        trace.set("context_used", objectMapper.createObjectNode());
        trace.put("related_event_type", "New Booking");
        ArrayNode related = objectMapper.createArrayNode();
        related.add("E_CHILD_1");
        related.add("E_CHILD_2");
        trace.set("related_source_event_indices", related);
        ObjectNode extractedFields = objectMapper.createObjectNode();
        extractedFields.put("trace_subtype", "general_request");
        trace.set("extracted_fields", extractedFields);
        trace.set("manual_review", objectMapper.nullNode());
        events.add(trace);
        root.set("message_events", events);
        return objectMapper.writeValueAsString(root);
    }

    private String s10NotificationRoot(String externalMessageId) {
        return """
                {
                  "source_message": {
                    "source_message_id": "%s",
                    "subject": "Informational message",
                    "from": "agent@example.test",
                    "cc": [],
                    "received_at": "2026-07-12T04:00:00Z",
                    "source_channel": "Email"
                  },
                  "route_code": "S10",
                  "handler_type": "main_agent_outcome",
                  "result_type": "source_message_review_notification",
                  "current_or_history": "current",
                  "agent_assessment": {
                    "status": "no_booking_action_detected",
                    "reason_code": "no_booking_action_detected",
                    "automation_action": "none"
                  },
                  "notification": {
                    "required": true,
                    "notification_type": "source_message_review",
                    "show_source_message": true,
                    "requires_user_decision": true,
                    "visible_message": "纯信息类邮件，请人工查看原邮件。"
                  },
                  "manual_review": null
                }
                """.formatted(externalMessageId);
    }

    private String s99NotificationRoot(String externalMessageId) {
        return """
                {
                  "source_message": {
                    "source_message_id": "%s",
                    "subject": "Material unavailable",
                    "from": "agent@example.test",
                    "cc": [],
                    "received_at": "2026-07-12T04:00:00Z",
                    "source_channel": "Email"
                  },
                  "route_code": "S99",
                  "handler_type": "main_agent_outcome",
                  "result_type": "source_message_review_notification",
                  "current_or_history": "current",
                  "agent_assessment": {
                    "status": "material_package_unavailable",
                    "reason_code": "material_package_unavailable",
                    "automation_action": "none"
                  },
                  "notification": {
                    "required": true,
                    "notification_type": "source_message_review",
                    "show_source_message": true,
                    "requires_user_decision": true,
                    "visible_message": "无法形成业务素材包，请人工查看原邮件。"
                  },
                  "manual_review": {
                    "reason_code": "material_package_unavailable",
                    "visible_reason": "邮件内容无法形成业务素材包。",
                    "review_record_type": "main_agent_entry_review",
                    "missing_fields": [],
                    "blocking_points": [],
                    "conflicting_points": [],
                    "suggested_human_actions": [],
                    "evidence_to_check": [],
                    "known_fields": {}
                  }
                }
                """.formatted(externalMessageId);
    }

    private String parentSplitBusinessRoot(String externalMessageId) {
        return """
                {
                  "source_message": {
                    "source_message_id": "%s",
                    "subject": "Parent split booking request",
                    "from": "agent@example.test",
                    "cc": [],
                    "received_at": "2026-07-12T04:00:00Z",
                    "source_channel": "Email"
                  },
                  "message_events": [
                    {
                      "event_type": "New Booking",
                      "event_role": "travel_agent_request",
                      "source_event_index": "E_CHILD_1",
                      "current_or_history": "current",
                      "case_keys": {
                        "group_code": "MCP-CHILD-A",
                        "confirmation_number": null,
                        "reservation_number": null,
                        "block_code": null
                      },
                      "relevant_message_excerpt": "Please create child group A.",
                      "attachments": [],
                      "file_references": [],
                      "context_used": {},
                      "extracted_fields": {
                        "booking_object_type": "Group Block",
                        "arrival_date": "2026-09-01",
                        "departure_date": "2026-09-03"
                      },
                      "manual_review": null
                    },
                    {
                      "event_type": "New Booking",
                      "event_role": "travel_agent_request",
                      "source_event_index": "E_CHILD_2",
                      "current_or_history": "current",
                      "case_keys": {
                        "group_code": "MCP-CHILD-B",
                        "confirmation_number": null,
                        "reservation_number": null,
                        "block_code": null
                      },
                      "relevant_message_excerpt": "Please create child group B.",
                      "attachments": [],
                      "file_references": [],
                      "context_used": {},
                      "extracted_fields": {
                        "booking_object_type": "Group Block",
                        "arrival_date": "2026-09-01",
                        "departure_date": "2026-09-03"
                      },
                      "manual_review": null
                    },
                    {
                      "event_type": "Cancel Allotment",
                      "event_role": "travel_agent_request",
                      "source_event_index": "E_PARENT",
                      "current_or_history": "current",
                      "case_keys": {
                        "group_code": "MCP-PARENT",
                        "confirmation_number": null,
                        "reservation_number": null,
                        "block_code": "MCP-PARENT"
                      },
                      "relevant_message_excerpt": "Release parent group after splitting allocation to child groups.",
                      "attachments": [],
                      "file_references": [],
                      "context_used": {},
                      "related_event_type": "New Booking",
                      "requires_downstream_hard_validation": true,
                      "related_source_event_indices": [
                        "E_CHILD_1",
                        "E_CHILD_2"
                      ],
                      "extracted_fields": {
                        "relationship_type": "linked_parent_release_after_child_split",
                        "parent_group_code": "MCP-PARENT",
                        "cancel_scope": "entire_allotment_control_block",
                        "parent_release_or_cancel_candidate": true,
                        "release_reason": "parent_to_child_allocation_split",
                        "allocation_split_from_parent": true,
                        "child_group_codes": [
                          "MCP-CHILD-A",
                          "MCP-CHILD-B"
                        ]
                      },
                      "manual_review": null
                    }
                  ],
                  "case_candidates": [],
                  "extraction_warnings": [],
                  "unhandled_current_intents": []
                }
                """.formatted(externalMessageId);
    }
}
