package com.starry.admin.api;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.reset;
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 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 com.starry.admin.common.apitest.ApiTestDataSeeder;
import com.starry.admin.common.task.OverdueOrderHandlerTask;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.entity.PlayOrderContinueInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderContinueInfoService;
import com.starry.admin.modules.weichat.service.NotificationSender;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import com.starry.common.constant.Constants;
import com.starry.common.context.CustomSecurityContextHolder;
import java.time.LocalDateTime;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;

class WxOrderInfoControllerApiTest extends WxCustomOrderApiTestSupport {

    private static final String MESSAGE_OPERATION_SUCCESS = "操作成功";
    private static final String REVIEW_PENDING_STATE = "0";
    private static final String EXCLUDE_HISTORY_DISABLED = "0";
    private static final String OTHER_CLERK_ID = "clerk-apitest-other";
    private static final int SINGLE_QUANTITY = 1;

    @MockBean
    private NotificationSender notificationSender;

    @MockBean
    private WxCustomMpService wxCustomMpService;

    @MockBean
    private OverdueOrderHandlerTask overdueOrderHandlerTask;

    @Autowired
    private IPlayOrderContinueInfoService orderContinueInfoService;

    private final ObjectMapper mapper = new ObjectMapper();
    private String customerToken;
    private String clerkToken;

    @BeforeEach
    void setUpTokens() {
        ensureTenantContext();
        customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
        customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
        clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
        clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
        // Relax notifications to avoid strict verification noise
        doNothing().when(notificationSender).sendOrderMessageAsync(Mockito.any());
        doNothing().when(notificationSender).sendOrderFinishMessageAsync(Mockito.any());
        doNothing().when(wxCustomMpService).sendCreateOrderMessageBatch(
                anyList(),
                Mockito.anyString(),
                Mockito.anyString(),
                Mockito.anyString(),
                Mockito.anyString(),
                Mockito.anyString(),
                Mockito.anyString());
        doNothing().when(overdueOrderHandlerTask).enqueue(Mockito.anyString());
    }

    @AfterEach
    void tearDown() {
        ensureTenantContext();
        orderContinueInfoService.lambdaUpdate()
                .eq(PlayOrderContinueInfoEntity::getClerkId, ApiTestDataSeeder.DEFAULT_CLERK_ID)
                .remove();
        reset(notificationSender, wxCustomMpService, overdueOrderHandlerTask);
        CustomSecurityContextHolder.remove();
    }

    @Test
    void selectRandomOrderByIdHidesCustomerContactWhenPending() throws Exception {
        String marker = "privacy-" + LocalDateTime.now().toString();
        String orderId = createRandomOrder(marker);

        MvcResult result = mockMvc.perform(get("/wx/order/clerk/selectRandomOrderById")
                        .param("id", orderId)
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andReturn();

        JsonNode data = mapper.readTree(result.getResponse().getContentAsString()).path("data");
        assertThat(data.path("weiChatCode").asText()).isEmpty();
        assertThat(data.path("placeType").asText()).isEqualTo(OrderConstant.PlaceType.RANDOM.getCode());
        assertThat(data.path("orderStatus").asText()).isEqualTo(OrderConstant.OrderStatus.PENDING.getCode());
    }

    @Test
    void duplicateContinuationRequestIsRejected() throws Exception {
        String marker = "continue-" + LocalDateTime.now().toString();
        String orderId = createRandomOrder(marker);

        acceptOrder(orderId);

        ArrayNode images = mapper.createArrayNode().add("https://example.com/proof.png");
        ObjectNode payload = mapper.createObjectNode()
                .put("orderId", orderId)
                .put("remark", "加场申请")
                .set("images", images);

        mockMvc.perform(post("/wx/order/clerk/continue")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
                        .contentType(MediaType.APPLICATION_JSON)
                .content(payload.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data").value("下单成功"))
                .andExpect(jsonPath("$.message").value(MESSAGE_OPERATION_SUCCESS));

        mockMvc.perform(post("/wx/order/clerk/continue")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(payload.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(500))
                .andExpect(jsonPath("$.message").value("同一场订单只能续单一次"));

        ensureTenantContext();
        PlayOrderContinueInfoEntity continuation = orderContinueInfoService.lambdaQuery()
                .eq(PlayOrderContinueInfoEntity::getOrderId, orderId)
                .last("limit 1")
                .one();
        assertThat(continuation).isNotNull();
        assertThat(continuation.getReviewedState()).isEqualTo(REVIEW_PENDING_STATE);
    }

    @Test
    void randomOrderAcceptedByAnotherClerkHidesSensitiveFields() throws Exception {
        String marker = "privacy-accepted-" + LocalDateTime.now();
        String orderId = createRandomOrder(marker);

        ensureTenantContext();
        playOrderInfoService.lambdaUpdate()
                .eq(PlayOrderInfoEntity::getId, orderId)
                .set(PlayOrderInfoEntity::getAcceptBy, OTHER_CLERK_ID)
                .set(PlayOrderInfoEntity::getOrderStatus, OrderConstant.OrderStatus.ACCEPTED.getCode())
                .update();

        MvcResult result = mockMvc.perform(get("/wx/order/clerk/selectRandomOrderById")
                        .param("id", orderId)
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andReturn();

        JsonNode data = mapper.readTree(result.getResponse().getContentAsString()).path("data");
        assertThat(data.path("acceptBy").asText()).isEqualTo(OTHER_CLERK_ID);
        assertThat(data.path("weiChatCode").asText()).isEmpty();
        assertThat(data.path("customId").asText()).isEmpty();
        String nickname = data.path("customNickname").asText();
        assertThat(nickname.equals("匿名用户") || "å¿åç¨æ·".equals(nickname)).isTrue();
        assertThat(data.path("customAvatar").asText()).isEmpty();
    }

    @Test
    void queryByIdFromClerkControllerHidesCustomerInfoForPendingRandomOrders() throws Exception {
        String marker = "privacy-leak-" + LocalDateTime.now().toString();
        String orderId = createRandomOrder(marker);

        // Access via the generic Clerk Order Detail endpoint (which the notification likely links to)
        MvcResult result = mockMvc.perform(get("/wx/clerk/order/queryById")
                        .param("id", orderId)
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andReturn();

        JsonNode data = mapper.readTree(result.getResponse().getContentAsString()).path("data");

        assertThat(data.path("weiChatCode").asText()).isEmpty();
        assertThat(data.path("placeType").asText()).isEqualTo(OrderConstant.PlaceType.RANDOM.getCode());
        assertThat(data.path("orderStatus").asText()).isEqualTo(OrderConstant.OrderStatus.PENDING.getCode());

        String nickname = data.path("customNickname").asText();
        assertThat(nickname.equals("匿名用户") || "å¿åç¨æ·".equals(nickname)).isTrue();
        assertThat(data.path("customAvatar").asText()).isEmpty();
    }

    @Test
    void clerkOrderListHidesCustomerInfoForPendingRandomOrders() throws Exception {
        String marker = "privacy-list-" + LocalDateTime.now().toString();
        String orderId = createRandomOrder(marker);

        // Ensure the created pending random order appears in the clerk's own order list
        ensureTenantContext();
        playOrderInfoService.lambdaUpdate()
                .eq(PlayOrderInfoEntity::getId, orderId)
                .set(PlayOrderInfoEntity::getAcceptBy, ApiTestDataSeeder.DEFAULT_CLERK_ID)
                .update();

        ObjectNode payload = mapper.createObjectNode();
        payload.put("pageNum", 1);
        payload.put("pageSize", 10);
        payload.put("orderStatus", OrderConstant.OrderStatus.PENDING.getCode());
        payload.put("placeType", OrderConstant.PlaceType.RANDOM.getCode());

        MvcResult result = mockMvc.perform(post("/wx/clerk/order/queryByPage")
                        .contentType(MediaType.APPLICATION_JSON)
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
                        .content(payload.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andReturn();

        JsonNode root = mapper.readTree(result.getResponse().getContentAsString());
        JsonNode data = root.path("data");
        JsonNode records = data.isArray() ? data : data.path("records");
        assertThat(records.isArray()).isTrue();
        assertThat(records.size()).isGreaterThan(0);

        boolean found = false;
        for (JsonNode node : records) {
            assertThat(node.path("placeType").asText()).isEqualTo(OrderConstant.PlaceType.RANDOM.getCode());
            assertThat(node.path("orderStatus").asText()).isEqualTo(OrderConstant.OrderStatus.PENDING.getCode());
            if (orderId.equals(node.path("id").asText())) {
                found = true;
                String nickname = node.path("customNickname").asText();
                assertThat(nickname.equals("匿名用户") || "å¿åç¨æ·".equals(nickname)).isTrue();
                assertThat(node.path("customAvatar").asText()).isEmpty();
                assertThat(node.path("customId").asText()).isEmpty();
            }
        }

        assertThat(found).as("Pending random order should appear in clerk list response").isTrue();
    }

    private String createRandomOrder(String remark) throws Exception {
        ensureTenantContext();
        resetCustomerBalance();

        ObjectNode payload = mapper.createObjectNode();
        payload.put("sex", OrderConstant.Gender.FEMALE.getCode());
        payload.put("levelId", ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
        payload.put("commodityId", ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
        payload.put("commodityQuantity", SINGLE_QUANTITY);
        payload.put("weiChatCode", "apitest-customer-wx");
        payload.put("excludeHistory", EXCLUDE_HISTORY_DISABLED);
        payload.set("couponIds", mapper.createArrayNode());
        payload.put("remark", remark);

        mockMvc.perform(post("/wx/custom/order/random")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(payload.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200));

        ensureTenantContext();
        PlayOrderInfoEntity order = playOrderInfoService.lambdaQuery()
                .eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
                .eq(PlayOrderInfoEntity::getRemark, remark)
                .orderByDesc(PlayOrderInfoEntity::getCreatedTime)
                .last("limit 1")
                .one();
        assertThat(order).isNotNull();
        return order.getId();
    }

    private void acceptOrder(String orderId) throws Exception {
        mockMvc.perform(get("/wx/clerk/order/accept")
                        .param("id", orderId)
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200));
    }
}
