package com.haha.admin.controller;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.haha.entity.Device;
import com.haha.entity.DeviceInventory;
import com.haha.entity.Replenisher;
import com.haha.entity.dto.ReplenishDTO;
import com.haha.service.DeviceInventoryService;
import com.haha.service.DeviceService;
import com.haha.service.ReplenisherService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
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 java.util.*;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * 补货员操作控制器 - 完整单元测试
 * <p>
 * 覆盖补货员端接口：个人信息、设备列表、库存查询、补货操作
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("补货员操作控制器")
class ReplenisherOperationControllerTest {

    @Mock
    private ReplenisherService replenisherService;
    @Mock
    private DeviceInventoryService deviceInventoryService;
    @Mock
    private DeviceService deviceService;

    @InjectMocks
    private ReplenisherOperationController controller;

    private MockMvc mockMvc;
    private ObjectMapper objectMapper;
    private MockedStatic<StpUtil> stpUtilMock;

    private Replenisher mockReplenisher;

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

        mockReplenisher = new Replenisher();
        mockReplenisher.setId(1L);
        mockReplenisher.setName("测试补货员");
        mockReplenisher.setPhone("13800138000");
        mockReplenisher.setEmployeeId("EMP001");
        mockReplenisher.setStatus(1);

        // 模拟补货员登录态
        SaSession mockSession = mock(SaSession.class);
        when(mockSession.getString("userType")).thenReturn("REPLENISHER");

        stpUtilMock = mockStatic(StpUtil.class);
        stpUtilMock.when(StpUtil::getTokenSession).thenReturn(mockSession);
        stpUtilMock.when(StpUtil::getLoginIdAsString).thenReturn("1");
    }

    @AfterEach
    void tearDown() {
        if (stpUtilMock != null) {
            stpUtilMock.close();
        }
    }

    // ============================================================
    // 1. GET /replenisher/my-info
    // ============================================================

    @Nested
    @DisplayName("GET /replenisher/my-info - 获取当前补货员信息")
    class MyInfoTests {

        @Test
        @DisplayName("成功获取补货员信息")
        void shouldGetMyInfo() throws Exception {
            mockReplenisher.setBoundDeviceCount(3);
            when(replenisherService.getById("1")).thenReturn(mockReplenisher);
            when(replenisherService.getReplenisherDetail(1L)).thenReturn(mockReplenisher);

            mockMvc.perform(get("/replenisher/my-info"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(200))
                    .andExpect(jsonPath("$.data.name").value("测试补货员"))
                    .andExpect(jsonPath("$.data.phone").value("13800138000"))
                    .andExpect(jsonPath("$.data.boundDeviceCount").value(3));

            verify(replenisherService).getReplenisherDetail(1L);
        }

        @Test
        @DisplayName("补货员不存在")
        void shouldReturn404WhenReplenisherNotFound() throws Exception {
            when(replenisherService.getById("1")).thenReturn(mockReplenisher);
            when(replenisherService.getReplenisherDetail(1L)).thenReturn(null);

            mockMvc.perform(get("/replenisher/my-info"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(404))
                    .andExpect(jsonPath("$.message").value("补货员不存在"));
        }
    }

    // ============================================================
    // 2. GET /replenisher/device/list
    // ============================================================

    @Nested
    @DisplayName("GET /replenisher/device/list - 获取绑定设备列表")
    class DeviceListTests {

        @Test
        @DisplayName("有绑定设备时返回设备及库存概览")
        void shouldReturnDeviceListWithInventory() throws Exception {
            when(replenisherService.getById("1")).thenReturn(mockReplenisher);
            when(replenisherService.getBoundDeviceIds(1L))
                    .thenReturn(Arrays.asList("DEV001", "DEV002"));

            Device dev1 = new Device();
            dev1.setDeviceId("DEV001");
            dev1.setName("设备一");
            dev1.setStatus(1);
            dev1.setAddress("一号门店");

            Device dev2 = new Device();
            dev2.setDeviceId("DEV002");
            dev2.setName("设备二");
            dev2.setStatus(0);
            dev2.setAddress("二号门店");

            when(deviceService.getDeviceBySn("DEV001")).thenReturn(dev1);
            when(deviceService.getDeviceBySn("DEV002")).thenReturn(dev2);

            // 设备一的库存：正常
            Map<String, Object> inv1_1 = new HashMap<>();
            inv1_1.put("stock", 10);
            inv1_1.put("warning_threshold", 5);
            List<Map<String, Object>> invList1 = Collections.singletonList(inv1_1);

            // 设备二的库存：缺货+低库存
            Map<String, Object> inv2_1 = new HashMap<>();
            inv2_1.put("stock", 0);
            inv2_1.put("warning_threshold", 5);
            Map<String, Object> inv2_2 = new HashMap<>();
            inv2_2.put("stock", 3);
            inv2_2.put("warning_threshold", 10);
            List<Map<String, Object>> invList2 = Arrays.asList(inv2_1, inv2_2);

            when(deviceInventoryService.getInventoryWithProduct("DEV001")).thenReturn(invList1);
            when(deviceInventoryService.getInventoryWithProduct("DEV002")).thenReturn(invList2);

            mockMvc.perform(get("/replenisher/device/list"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(200))
                    .andExpect(jsonPath("$.data.length()").value(2))
                    // 设备一
                    .andExpect(jsonPath("$.data[0].deviceId").value("DEV001"))
                    .andExpect(jsonPath("$.data[0].name").value("设备一"))
                    .andExpect(jsonPath("$.data[0].status").value(1))
                    .andExpect(jsonPath("$.data[0].statusLabel").value("在线"))
                    .andExpect(jsonPath("$.data[0].totalStock").value(10))
                    .andExpect(jsonPath("$.data[0].lowStockCount").value(0))
                    .andExpect(jsonPath("$.data[0].zeroStockCount").value(0))
                    .andExpect(jsonPath("$.data[0].productCount").value(1))
                    // 设备二
                    .andExpect(jsonPath("$.data[1].deviceId").value("DEV002"))
                    .andExpect(jsonPath("$.data[1].statusLabel").value("离线"))
                    .andExpect(jsonPath("$.data[1].totalStock").value(3))
                    .andExpect(jsonPath("$.data[1].lowStockCount").value(1))
                    .andExpect(jsonPath("$.data[1].zeroStockCount").value(1))
                    .andExpect(jsonPath("$.data[1].productCount").value(2));
        }

        @Test
        @DisplayName("无绑定设备时返回空列表")
        void shouldReturnEmptyWhenNoDevices() throws Exception {
            when(replenisherService.getById("1")).thenReturn(mockReplenisher);
            when(replenisherService.getBoundDeviceIds(1L))
                    .thenReturn(Collections.emptyList());

            mockMvc.perform(get("/replenisher/device/list"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.data.length()").value(0));
        }
    }

    // ============================================================
    // 3. GET /replenisher/device/inventory/{deviceId}
    // ============================================================

    @Nested
    @DisplayName("GET /replenisher/device/inventory/{deviceId} - 获取设备库存详情")
    class DeviceInventoryTests {

        @Test
        @DisplayName("成功获取设备库存详情")
        void shouldGetDeviceInventory() throws Exception {
            when(replenisherService.getById("1")).thenReturn(mockReplenisher);
            when(replenisherService.getBoundDeviceIds(1L))
                    .thenReturn(Collections.singletonList("DEV001"));

            Device device = new Device();
            device.setDeviceId("DEV001");
            device.setName("测试设备");
            device.setAddress("测试地址");
            when(deviceService.getDeviceBySn("DEV001")).thenReturn(device);

            when(deviceInventoryService.getInventoryWithProduct("DEV001"))
                    .thenReturn(Collections.emptyList());

            mockMvc.perform(get("/replenisher/device/inventory/DEV001"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(200))
                    .andExpect(jsonPath("$.data.deviceId").value("DEV001"))
                    .andExpect(jsonPath("$.data.name").value("测试设备"))
                    .andExpect(jsonPath("$.data.address").value("测试地址"))
                    .andExpect(jsonPath("$.data.inventoryList.length()").value(0));
        }

        @Test
        @DisplayName("无权查看未绑定设备")
        void shouldReturn403WhenDeviceNotBound() throws Exception {
            when(replenisherService.getById("1")).thenReturn(mockReplenisher);
            when(replenisherService.getBoundDeviceIds(1L))
                    .thenReturn(Collections.singletonList("DEV001")); // 只绑定了DEV001

            mockMvc.perform(get("/replenisher/device/inventory/DEV999"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(403))
                    .andExpect(jsonPath("$.message").value("您无权查看此设备的库存"));
        }

        @Test
        @DisplayName("设备不存在")
        void shouldReturn404WhenDeviceNotFound() throws Exception {
            when(replenisherService.getById("1")).thenReturn(mockReplenisher);
            when(replenisherService.getBoundDeviceIds(1L))
                    .thenReturn(Collections.singletonList("DEV001"));
            when(deviceService.getDeviceBySn("DEV001")).thenReturn(null);

            mockMvc.perform(get("/replenisher/device/inventory/DEV001"))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(404))
                    .andExpect(jsonPath("$.message").value("设备不存在"));
        }
    }

    // ============================================================
    // 4. POST /replenisher/stock/replenish
    // ============================================================

    @Nested
    @DisplayName("POST /replenisher/stock/replenish - 执行补货操作")
    class ReplenishStockTests {

        @Test
        @DisplayName("补货全部成功")
        void shouldReplenishAllSuccess() throws Exception {
            when(replenisherService.getById("1")).thenReturn(mockReplenisher);
            when(replenisherService.getBoundDeviceIds(1L))
                    .thenReturn(Collections.singletonList("DEV001"));

            ReplenishDTO dto = new ReplenishDTO();
            dto.setDeviceId("DEV001");

            ReplenishDTO.ReplenishItem item1 = new ReplenishDTO.ReplenishItem();
            item1.setProductId(1L);
            item1.setProductCode("P001");
            item1.setProductName("商品一");
            item1.setQuantity(5);
            item1.setShelfNum(1);
            item1.setPosition("A1");

            ReplenishDTO.ReplenishItem item2 = new ReplenishDTO.ReplenishItem();
            item2.setProductId(2L);
            item2.setProductCode("P002");
            item2.setProductName("商品二");
            item2.setQuantity(3);
            item2.setShelfNum(1);
            item2.setPosition("A2");

            dto.setItems(Arrays.asList(item1, item2));

            DeviceInventory inv1 = new DeviceInventory();
            inv1.setStock(15);
            DeviceInventory inv2 = new DeviceInventory();
            inv2.setStock(8);

            when(deviceInventoryService.increaseStock(
                    eq("DEV001"), eq(1L), eq("P001"), eq("商品一"),
                    eq(5), eq(1), eq("A1"), eq(1L), eq("测试补货员"), isNull()))
                    .thenReturn(inv1);
            when(deviceInventoryService.increaseStock(
                    eq("DEV001"), eq(2L), eq("P002"), eq("商品二"),
                    eq(3), eq(1), eq("A2"), eq(1L), eq("测试补货员"), isNull()))
                    .thenReturn(inv2);

            mockMvc.perform(post("/replenisher/stock/replenish")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(dto)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(200))
                    .andExpect(jsonPath("$.data.total").value(2))
                    .andExpect(jsonPath("$.data.success").value(2))
                    .andExpect(jsonPath("$.data.details.length()").value(2))
                    .andExpect(jsonPath("$.data.details[0].success").value(true))
                    .andExpect(jsonPath("$.data.details[1].success").value(true));
        }

        @Test
        @DisplayName("补货部分失败")
        void shouldReplenishPartialFailure() throws Exception {
            when(replenisherService.getById("1")).thenReturn(mockReplenisher);
            when(replenisherService.getBoundDeviceIds(1L))
                    .thenReturn(Collections.singletonList("DEV001"));

            ReplenishDTO dto = new ReplenishDTO();
            dto.setDeviceId("DEV001");

            ReplenishDTO.ReplenishItem item1 = new ReplenishDTO.ReplenishItem();
            item1.setProductId(1L);
            item1.setProductName("商品一");
            item1.setQuantity(5);

            ReplenishDTO.ReplenishItem item2 = new ReplenishDTO.ReplenishItem();
            item2.setProductId(2L);
            item2.setProductName("商品二");
            item2.setQuantity(3);

            dto.setItems(Arrays.asList(item1, item2));

            DeviceInventory inv1 = new DeviceInventory();
            inv1.setStock(5);

            when(deviceInventoryService.increaseStock(
                    eq("DEV001"), eq(1L), isNull(), eq("商品一"),
                    eq(5), isNull(), isNull(), eq(1L), eq("测试补货员"), isNull()))
                    .thenReturn(inv1);
            when(deviceInventoryService.increaseStock(
                    eq("DEV001"), eq(2L), isNull(), eq("商品二"),
                    eq(3), isNull(), isNull(), eq(1L), eq("测试补货员"), isNull()))
                    .thenThrow(new RuntimeException("库存不足"));

            mockMvc.perform(post("/replenisher/stock/replenish")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(dto)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(200))
                    .andExpect(jsonPath("$.data.total").value(2))
                    .andExpect(jsonPath("$.data.success").value(1))
                    .andExpect(jsonPath("$.data.details[0].success").value(true))
                    .andExpect(jsonPath("$.data.details[1].success").value(false))
                    .andExpect(jsonPath("$.data.details[1].error").value("库存不足"));
        }

        @Test
        @DisplayName("无权对未绑定设备补货")
        void shouldReturn403WhenDeviceNotBound() throws Exception {
            when(replenisherService.getById("1")).thenReturn(mockReplenisher);
            when(replenisherService.getBoundDeviceIds(1L))
                    .thenReturn(Collections.singletonList("DEV001")); // 只绑定DEV001

            ReplenishDTO dto = new ReplenishDTO();
            dto.setDeviceId("DEV999");
            dto.setItems(Collections.emptyList());

            mockMvc.perform(post("/replenisher/stock/replenish")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(dto)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(403))
                    .andExpect(jsonPath("$.message").value("您无权对此设备进行补货操作"));
        }
    }
}
