package com.ruoyi.web.modules.cart.controller;

import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
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.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.math.BigDecimal;
import java.util.Collections;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.util.NestedServletException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.modules.account.support.MemberContext;
import com.ruoyi.web.modules.cart.dto.CartItemAddDTO;
import com.ruoyi.web.modules.cart.service.ICartAppService;
import com.ruoyi.web.modules.cart.vo.CartCheckedSummaryVO;
import com.ruoyi.web.modules.cart.vo.CartItemAddResultVO;
import com.ruoyi.web.modules.cart.vo.CartListVO;

/**
 * C 端购物车接口测试
 */
@ExtendWith(MockitoExtension.class)
class CartAppControllerTest
{
    private static final Long MEMBER_ID = 200L;

    @Mock
    private ICartAppService cartAppService;

    @InjectMocks
    private CartAppController controller;

    private MockMvc mockMvc;

    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp()
    {
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
        objectMapper = new ObjectMapper();
    }

    @AfterEach
    void tearDown()
    {
        MemberContext.clear();
    }

    @Test
    void list_requiresLogin() throws Exception
    {
        NestedServletException ex = assertThrows(NestedServletException.class,
                () -> mockMvc.perform(get("/api/cart")).andReturn());
        assertInstanceOf(ServiceException.class, ex.getCause());
    }

    @Test
    void list_returns200() throws Exception
    {
        MemberContext.setMemberId(MEMBER_ID);
        CartListVO list = new CartListVO();
        list.setItems(Collections.emptyList());
        CartCheckedSummaryVO summary = new CartCheckedSummaryVO();
        summary.setCheckedCount(0);
        summary.setCheckedQuantity(0);
        summary.setCheckedAmount(BigDecimal.ZERO);
        list.setCheckedSummary(summary);
        when(cartAppService.list(MEMBER_ID)).thenReturn(list);

        mockMvc.perform(get("/api/cart"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data.items").isArray());

        verify(cartAppService).list(MEMBER_ID);
    }

    @Test
    void addItem_returns200() throws Exception
    {
        MemberContext.setMemberId(MEMBER_ID);
        when(cartAppService.addItem(eq(MEMBER_ID), any(CartItemAddDTO.class)))
                .thenReturn(new CartItemAddResultVO(10L, 2));

        CartItemAddDTO dto = new CartItemAddDTO();
        dto.setGoodsId(1L);
        dto.setQuantity(2);

        mockMvc.perform(post("/api/cart/items")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(dto)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data.cartItemId").value(10))
                .andExpect(jsonPath("$.data.quantity").value(2));
    }

    @Test
    void removeItem_returns200() throws Exception
    {
        MemberContext.setMemberId(MEMBER_ID);

        mockMvc.perform(delete("/api/cart/items/10"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200));

        verify(cartAppService).removeItem(MEMBER_ID, 10L);
    }

    @Test
    void cleanInvalid_returns200() throws Exception
    {
        MemberContext.setMemberId(MEMBER_ID);

        mockMvc.perform(delete("/api/cart/invalid"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200));

        verify(cartAppService).cleanInvalid(MEMBER_ID);
    }

    @Test
    void updateQuantity_returns200() throws Exception
    {
        MemberContext.setMemberId(MEMBER_ID);
        when(cartAppService.updateQuantity(eq(MEMBER_ID), eq(10L), any()))
                .thenReturn(new CartItemAddResultVO(10L, 3));

        mockMvc.perform(put("/api/cart/items/10/quantity")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"quantity\":3}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.quantity").value(3));
    }
}
