package cn.nianxx.thhotel.workflows.reservation.control;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
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 java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Instant;
import java.util.HexFormat;
import java.util.List;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
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.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest(
        classes = ThHotelApplication.class,
        properties = {
                "superagent.task-result.hmac-secret=test-superagent-secret",
                "superagent.task-result.clock-skew-seconds=300",
                "superagent.task-result.nonce-ttl-seconds=600",
                "superagent.task-result.max-body-bytes=12000",
                "superagent.task-result.allow-legacy-internal-source-message-id=false"
        })
@AutoConfigureMockMvc
@ActiveProfiles("test")
class SuperAgentTaskResultStrictSourceMessageIdControllerTest {

    private static final String ENDPOINT = "/api/integrations/superagent/task-results";
    private static final String CLIENT_ID = "superagent-test-client";
    private static final String SECRET = "test-superagent-secret";

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private SourceMessageCaptureService captureService;

    @Test
    void shouldRejectInternalSourceMessageIdWhenLegacyCompatibilityDisabled() throws Exception {
        SourceMessageCaptureResult source = captureSourceMessage("mail-strict-external-id-only-001");
        String body = """
                {
                  "source_message_id": "%s",
                  "ai_task_results": [
                    {
                      "source_event_index": 1,
                      "catalog_code": "S01",
                      "skill_id": "S01_new_booking_skill",
                      "result_type": "normal_task",
                      "task_type": "New Booking",
                      "task_subtype": "new_fit_reservation",
                      "current_or_history": "current",
                      "visible_reason": "AI extracted a task result.",
                      "relevant_message_excerpt": "Please handle booking message.",
                      "attachments": [],
                      "file_references": [],
                      "context_used": {},
                      "case_keys": {},
                      "extracted_fields": {},
                      "additional_operations": [],
                      "idempotency_key": null
                    }
                  ],
                  "extraction_warnings": []
                }
                """.formatted(source.inboxId());

        String timestamp = Instant.now().toString();
        String nonce = "nonce-strict-external-id-only-001";
        mockMvc.perform(post(ENDPOINT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(body)
                        .header("X-TH-Hotel-SuperAgent-Client-Id", CLIENT_ID)
                        .header("X-TH-Hotel-SuperAgent-Timestamp", timestamp)
                        .header("X-TH-Hotel-SuperAgent-Nonce", nonce)
                        .header("X-TH-Hotel-SuperAgent-Signature", signature(body, nonce, timestamp)))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.error_code").value("SOURCE_MESSAGE_NOT_FOUND"));
    }

    private SourceMessageCaptureResult captureSourceMessage(String externalMessageId) {
        return captureService.capture(new CaptureSourceMessageCommand(
                "HOTEL-TEST",
                "AGENTBUS",
                "OUTLOOK",
                externalMessageId,
                "thread-" + externalMessageId,
                "frame-" + externalMessageId,
                "session-m002",
                Instant.parse("2026-07-07T08:00:00Z"),
                "guest@example.test",
                "M002 SuperAgent intake",
                "Please handle booking message.",
                "<html><body>Please handle booking message.</body></html>",
                "{\"source\":{\"external_message_id\":\"" + externalMessageId + "\"}}",
                "agentbus-outlook-v1",
                List.of()
        ));
    }

    private String signature(String body, String nonce, String timestamp) throws Exception {
        String canonical = "POST\n"
                + ENDPOINT
                + "\n"
                + timestamp
                + "\n"
                + nonce
                + "\n"
                + CLIENT_ID
                + "\n"
                + sha256(body);
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        return "sha256=" + HexFormat.of().formatHex(mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8)));
    }

    private String sha256(String body) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        return HexFormat.of().formatHex(digest.digest(body.getBytes(StandardCharsets.UTF_8)));
    }
}
