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

import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.blankOrNullString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
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 com.jayway.jsonpath.JsonPath;
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.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

@SpringBootTest(
        classes = ThHotelApplication.class,
        properties = {
                "reservation.demo-data.enabled=true",
                "reservation.demo-data.access-key=test-demo-data-key"
        })
@AutoConfigureMockMvc
@ActiveProfiles("test")
class ReservationDemoDataControllerTest {

    private static final String HOTEL_ID = "HOTEL-TEST";
    private static final String ACCESS_KEY = "test-demo-data-key";

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldRejectDemoDataSeedWhenAccessKeyMissing() throws Exception {
        mockMvc.perform(post("/api/system/reservation/demo-data")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("""
                                {
                                  "hotel_id": "HOTEL-TEST"
                                }
                                """))
                .andExpect(status().isForbidden());
    }

    @Test
    void shouldSeedFrontendDemoDataAndMakeItQueryable() throws Exception {
        MvcResult seedResult = mockMvc.perform(post("/api/system/reservation/demo-data")
                        .header("X-TH-Hotel-Demo-Data-Key", ACCESS_KEY)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("""
                                {
                                  "hotel_id": "HOTEL-TEST",
                                  "run_label": "controller-test"
                                }
                                """))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.demo_run_id", not(blankOrNullString())))
                .andExpect(jsonPath("$.hotel_id").value(HOTEL_ID))
                .andExpect(jsonPath("$.source_messages", hasSize(greaterThanOrEqualTo(5))))
                .andExpect(jsonPath("$.orders", hasSize(greaterThanOrEqualTo(5))))
                .andExpect(jsonPath("$.tasks", hasSize(greaterThanOrEqualTo(6))))
                .andExpect(jsonPath("$.entrypoints.task_list_url", not(blankOrNullString())))
                .andReturn();

        String response = seedResult.getResponse().getContentAsString();
        String demoRunId = JsonPath.read(response, "$.demo_run_id");
        String queueSourceMessageId = first(response, "$.source_messages[?(@.scenario_code=='QUEUE_BLOCKED')].source_message_id");
        String queueOrderId = first(response, "$.orders[?(@.scenario_code=='QUEUE_BLOCKED')].order_id");
        String blockedTaskId = first(response, "$.tasks[?(@.scenario_code=='QUEUE_BLOCKED')].task_id");
        String failedTaskId = first(response, "$.tasks[?(@.scenario_code=='OPERA_FAILED')].task_id");

        mockMvc.perform(get("/api/reservation/tasks")
                        .param("hotel_id", HOTEL_ID)
                        .param("keyword", demoRunId)
                        .param("page_num", "1")
                        .param("page_size", "20"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.items", hasSize(greaterThanOrEqualTo(5))));

        mockMvc.perform(get("/api/reservation/orders")
                        .param("hotel_id", HOTEL_ID)
                        .param("keyword", demoRunId)
                        .param("page_num", "1")
                        .param("page_size", "20"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.items", hasSize(greaterThanOrEqualTo(5))));

        mockMvc.perform(get("/api/reservation/orders/{orderId}", queueOrderId)
                        .param("hotel_id", HOTEL_ID)
                        .param("include_tasks", "true")
                        .param("include_source_summary", "true"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.tasks", hasSize(greaterThanOrEqualTo(2))))
                .andExpect(jsonPath("$.tasks[1].readonly_reason_code").value("PREVIOUS_TASK_NOT_FINISHED"));

        mockMvc.perform(get("/api/reservation/tasks/{taskId}", blockedTaskId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.availability.blocked").value(true))
                .andExpect(jsonPath("$.availability.read_only").value(true))
                .andExpect(jsonPath("$.availability.blocked_by_task_id", not(blankOrNullString())));

        mockMvc.perform(get("/api/reservation/tasks/{taskId}", failedTaskId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.opera_operations[0].operation_status").value("FAILED"))
                .andExpect(jsonPath("$.opera_operations[0].attempt_count").value(1));

        mockMvc.perform(get("/api/source-messages/{sourceMessageId}/conversation", queueSourceMessageId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.messages", hasSize(greaterThanOrEqualTo(2))))
                .andExpect(jsonPath("$.messages[0].html_sanitize_required").value(true))
                .andExpect(jsonPath("$.messages[0].attachments", hasSize(greaterThanOrEqualTo(1))));
    }

    private String first(String json, String path) {
        List<String> values = JsonPath.read(json, path);
        return values.get(0);
    }
}
