package com.ruoyi.web.modules.order.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.modules.delivery.service.IShopDeliveryService;
import com.ruoyi.web.modules.delivery.vo.DeliveryRegionVO;
import com.ruoyi.web.modules.delivery.vo.ShopDeliverySettingVO;
import com.ruoyi.web.modules.order.constant.OrderConstants;
import com.ruoyi.web.modules.order.domain.BizOrder;
import com.ruoyi.web.modules.order.domain.BizOrderItem;
import com.ruoyi.web.modules.order.domain.BizOrderLogisticsTrace;
import com.ruoyi.web.modules.order.dto.OrderDeliveredDTO;
import com.ruoyi.web.modules.order.dto.OrderLogisticsDTO;
import com.ruoyi.web.modules.order.dto.OrderShipDTO;
import com.ruoyi.web.modules.order.dto.SellerOrderQuery;
import com.ruoyi.web.modules.order.mapper.BizOrderItemMapper;
import com.ruoyi.web.modules.order.mapper.BizOrderLogisticsTraceMapper;
import com.ruoyi.web.modules.order.mapper.BizOrderMapper;
import com.ruoyi.web.modules.order.service.impl.OrderServiceImpl;
import com.ruoyi.web.modules.order.vo.OrderDetailVO;
import com.ruoyi.web.modules.order.vo.OrderListRowVO;
import com.ruoyi.web.modules.order.vo.SellerShipBadgeVO;

@ExtendWith(MockitoExtension.class)
class OrderServiceImplTest
{
    @Mock
    private BizOrderMapper orderMapper;

    @Mock
    private BizOrderItemMapper orderItemMapper;

    @Mock
    private BizOrderLogisticsTraceMapper traceMapper;

    @Mock
    private IShopDeliveryService shopDeliveryService;

    @InjectMocks
    private OrderServiceImpl orderService;

    @Test
    void selectSellerList_filtersByShopId()
    {
        BizOrder order101 = order(1L, 101L, OrderConstants.STATUS_PENDING_SHIP);
        when(orderMapper.selectSellerList(eq(101L), any())).thenReturn(Collections.singletonList(order101));
        when(orderItemMapper.selectByOrderIds(Collections.singletonList(1L))).thenReturn(Collections.emptyList());

        orderService.selectSellerList(101L, new SellerOrderQuery());

        verify(orderMapper).selectSellerList(eq(101L), any());
    }

    @Test
    void selectSellerList_masksMobile()
    {
        BizOrder order = order(1L, 101L, OrderConstants.STATUS_PENDING_SHIP);
        order.setConsigneeMobile("13800138000");
        when(orderMapper.selectSellerList(eq(101L), any())).thenReturn(Collections.singletonList(order));
        when(orderItemMapper.selectByOrderIds(Collections.singletonList(1L))).thenReturn(Collections.emptyList());

        OrderListRowVO row = orderService.selectSellerList(101L, new SellerOrderQuery()).get(0);

        assertTrue(row.getConsigneeMobile().contains("*"));
        assertFalse(row.getConsigneeMobile().equals("13800138000"));
    }

    @Test
    void selectSellerShipDetail_success()
    {
        BizOrder order = order(10L, 101L, OrderConstants.STATUS_PENDING_SHIP);
        when(orderMapper.selectById(10L)).thenReturn(order);
        when(orderItemMapper.selectByOrderId(10L)).thenReturn(Collections.emptyList());
        when(traceMapper.selectByOrderId(10L)).thenReturn(Collections.emptyList());

        OrderDetailVO vo = orderService.selectSellerShipDetail(101L, 10L);

        assertNotNull(vo);
        assertEquals(10L, vo.getOrderId());
    }

    @Test
    void selectSellerShipDetail_rejectsClosedOrder()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_CLOSED));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> orderService.selectSellerShipDetail(101L, 10L));
        assertTrue(ex.getMessage().contains("不存在或无权访问"));
    }

    @Test
    void countSellerShipBadges_delegatesMapper()
    {
        when(orderMapper.countShopByStatus(101L, OrderConstants.STATUS_PENDING_SHIP)).thenReturn(5);
        when(orderMapper.countShopByStatus(101L, OrderConstants.STATUS_SHIPPED)).thenReturn(2);
        when(orderMapper.countShopByStatus(101L, OrderConstants.STATUS_COMPLETED)).thenReturn(10);

        SellerShipBadgeVO badges = orderService.countSellerShipBadges(101L);

        assertEquals(5, badges.getPendingShipCount());
        assertEquals(2, badges.getShippedCount());
        assertEquals(10, badges.getCompletedCount());
        verify(orderMapper).countShopByStatus(101L, OrderConstants.STATUS_PENDING_SHIP);
        verify(orderMapper).countShopByStatus(101L, OrderConstants.STATUS_SHIPPED);
        verify(orderMapper).countShopByStatus(101L, OrderConstants.STATUS_COMPLETED);
    }

    @Test
    void selectSellerDetail_success()
    {
        BizOrder order = order(10L, 101L, OrderConstants.STATUS_PENDING_SHIP);
        when(orderMapper.selectById(10L)).thenReturn(order);
        when(orderItemMapper.selectByOrderId(10L)).thenReturn(Collections.emptyList());
        when(traceMapper.selectByOrderId(10L)).thenReturn(Collections.emptyList());

        OrderDetailVO vo = orderService.selectSellerDetail(101L, 10L);

        assertNotNull(vo);
        assertEquals(10L, vo.getOrderId());
    }

    @Test
    void selectSellerDetail_crossShopRejected()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 201L, OrderConstants.STATUS_PENDING_SHIP));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> orderService.selectSellerDetail(101L, 10L));
        assertTrue(ex.getMessage().contains("不存在或无权访问"));
    }

    @Test
    void shipOrder_success()
    {
        BizOrder order = order(10L, 101L, OrderConstants.STATUS_PENDING_SHIP);
        when(orderMapper.selectById(10L)).thenReturn(order);
        when(shopDeliveryService.getByShopId(101L)).thenReturn(deliverySetting());
        when(orderMapper.updateForShip(any())).thenReturn(1);

        OrderShipDTO dto = logisticsShipDto();
        orderService.shipOrder(101L, 10L, dto, "seller");

        ArgumentCaptor<BizOrder> captor = ArgumentCaptor.forClass(BizOrder.class);
        verify(orderMapper).updateForShip(captor.capture());
        assertEquals("张三", captor.getValue().getShipperName());
        assertEquals("13800138000", captor.getValue().getShipperMobile());
        assertEquals("540000", captor.getValue().getShipRegionCode());
        assertEquals("西藏自治区 / 拉萨市 / 城关区", captor.getValue().getShipRegionName());
        assertEquals("XX路1号", captor.getValue().getShipDetailAddress());
        verify(traceMapper).insertTrace(any());
    }

    @Test
    void shipOrder_rejectsMissingDeliverySetting()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_PENDING_SHIP));
        when(shopDeliveryService.getByShopId(101L)).thenReturn(null);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> orderService.shipOrder(101L, 10L, logisticsShipDto(), "seller"));
        assertEquals(OrderConstants.MSG_SHIP_DELIVERY_SETTING_REQUIRED, ex.getMessage());
        verify(orderMapper, never()).updateForShip(any());
    }

    @Test
    void shipOrder_rejectsNonPendingShip()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_SHIPPED));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> orderService.shipOrder(101L, 10L, logisticsShipDto(), "seller"));
        assertEquals(OrderConstants.MSG_CANNOT_SHIP, ex.getMessage());
        verify(orderMapper, never()).updateForShip(any());
        verify(shopDeliveryService, never()).getByShopId(any());
    }

    @Test
    void shipOrder_rejectsMissingTrackingNo()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_PENDING_SHIP));
        OrderShipDTO dto = logisticsShipDto();
        dto.setTrackingNo(null);

        assertThrows(ServiceException.class, () -> orderService.shipOrder(101L, 10L, dto, "seller"));
        verify(orderMapper, never()).updateForShip(any());
        verify(shopDeliveryService, never()).getByShopId(any());
    }

    @Test
    void shipOrder_rejectsMissingCourier()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_PENDING_SHIP));
        OrderShipDTO dto = merchantShipDto();
        dto.setCourierName(null);

        assertThrows(ServiceException.class, () -> orderService.shipOrder(101L, 10L, dto, "seller"));
        verify(orderMapper, never()).updateForShip(any());
    }

    @Test
    void shipOrder_concurrentRejectWhenUpdateZero()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_PENDING_SHIP));
        when(shopDeliveryService.getByShopId(101L)).thenReturn(deliverySetting());
        when(orderMapper.updateForShip(any())).thenReturn(0);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> orderService.shipOrder(101L, 10L, logisticsShipDto(), "seller"));
        assertEquals(OrderConstants.MSG_CANNOT_SHIP, ex.getMessage());
    }

    private ShopDeliverySettingVO deliverySetting()
    {
        ShopDeliverySettingVO vo = new ShopDeliverySettingVO();
        vo.setShipperName("张三");
        vo.setShipperMobile("13800138000");
        vo.setDetailAddress("XX路1号");
        DeliveryRegionVO region = new DeliveryRegionVO();
        region.setRegionCode("540000");
        region.setProvinceName("西藏自治区");
        region.setCityName("拉萨市");
        region.setDistrictName("城关区");
        vo.setRegion(region);
        return vo;
    }

    @Test
    void appendLogistics_appendsTraceWithoutStatusChange()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_SHIPPED));

        OrderLogisticsDTO dto = traceDto("到达中转站");
        orderService.appendLogistics(101L, 10L, dto, "seller");
        orderService.appendLogistics(101L, 10L, traceDto("派送中"), "seller");

        ArgumentCaptor<BizOrderLogisticsTrace> captor = ArgumentCaptor.forClass(BizOrderLogisticsTrace.class);
        verify(traceMapper, times(2)).insertTrace(captor.capture());
        assertEquals(OrderConstants.TRACE_TRANSIT, captor.getAllValues().get(0).getTraceType());
        verify(orderMapper, never()).updateForShip(any());
    }

    @Test
    void appendLogistics_rejectsNonShipped()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_PENDING_SHIP));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> orderService.appendLogistics(101L, 10L, traceDto("x"), "seller"));
        assertEquals(OrderConstants.MSG_CANNOT_LOGISTICS, ex.getMessage());
    }

    @Test
    void registerDelivered_keepsShippedStatus()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_SHIPPED));

        OrderDeliveredDTO dto = new OrderDeliveredDTO();
        dto.setTraceTime(new Date());
        dto.setContent("已送达");
        orderService.registerDelivered(101L, 10L, dto, "seller");

        ArgumentCaptor<BizOrderLogisticsTrace> captor = ArgumentCaptor.forClass(BizOrderLogisticsTrace.class);
        verify(traceMapper).insertTrace(captor.capture());
        assertEquals(OrderConstants.TRACE_DELIVERED, captor.getValue().getTraceType());
        verify(orderMapper, never()).updateForShip(any());
    }

    @Test
    void deleteClosedOrder_success()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_CLOSED));
        when(orderMapper.updateForDelete(10L, 101L, "seller")).thenReturn(1);

        orderService.deleteClosedOrder(101L, 10L, "seller");

        verify(orderMapper).updateForDelete(10L, 101L, "seller");
    }

    @Test
    void deleteClosedOrder_rejectsNonClosed()
    {
        when(orderMapper.selectById(10L)).thenReturn(order(10L, 101L, OrderConstants.STATUS_PENDING_SHIP));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> orderService.deleteClosedOrder(101L, 10L, "seller"));
        assertEquals(OrderConstants.MSG_CANNOT_DELETE, ex.getMessage());
    }

    @Test
    void selectSellerDetail_traceListDescOrder()
    {
        BizOrder order = order(10L, 101L, OrderConstants.STATUS_SHIPPED);
        when(orderMapper.selectById(10L)).thenReturn(order);
        when(orderItemMapper.selectByOrderId(10L)).thenReturn(Collections.emptyList());

        Date early = new Date(1_000_000L);
        Date late = new Date(2_000_000L);
        BizOrderLogisticsTrace t1 = trace(1L, early);
        BizOrderLogisticsTrace t2 = trace(2L, late);
        when(traceMapper.selectByOrderId(10L)).thenReturn(Arrays.asList(t2, t1));

        OrderDetailVO vo = orderService.selectSellerDetail(101L, 10L);

        assertEquals(late, vo.getTraceList().get(0).getTraceTime());
        assertEquals(early, vo.getTraceList().get(1).getTraceTime());
    }

    @Test
    void hasUnfinishedOrdersByShop_delegatesMapper()
    {
        when(orderMapper.existsUnfinishedByShop(101L)).thenReturn(1);
        assertTrue(orderService.hasUnfinishedOrdersByShop(101L));
    }

    private BizOrder order(Long orderId, Long shopId, String status)
    {
        BizOrder order = new BizOrder();
        order.setOrderId(orderId);
        order.setShopId(shopId);
        order.setOrderStatus(status);
        order.setOrderNo("O202605260001");
        order.setPayAmount(BigDecimal.TEN);
        order.setFreightAmount(BigDecimal.ZERO);
        order.setCreateTime(new Date());
        return order;
    }

    private OrderShipDTO logisticsShipDto()
    {
        OrderShipDTO dto = new OrderShipDTO();
        dto.setShipTime(new Date());
        dto.setDeliveryType(OrderConstants.DELIVERY_LOGISTICS);
        dto.setLogisticsCompany("顺丰");
        dto.setTrackingNo("SF123456");
        return dto;
    }

    private OrderShipDTO merchantShipDto()
    {
        OrderShipDTO dto = new OrderShipDTO();
        dto.setShipTime(new Date());
        dto.setDeliveryType(OrderConstants.DELIVERY_MERCHANT);
        dto.setVehicleNo("藏A12345");
        dto.setCourierName("张三");
        dto.setCourierMobile("13800138001");
        return dto;
    }

    private OrderLogisticsDTO traceDto(String content)
    {
        OrderLogisticsDTO dto = new OrderLogisticsDTO();
        dto.setTraceTime(new Date());
        dto.setContent(content);
        return dto;
    }

    private BizOrderLogisticsTrace trace(Long id, Date time)
    {
        BizOrderLogisticsTrace trace = new BizOrderLogisticsTrace();
        trace.setTraceId(id);
        trace.setTraceType(OrderConstants.TRACE_TRANSIT);
        trace.setTraceTime(time);
        trace.setContent("node");
        return trace;
    }
}
