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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
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.List;
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.system.service.ISysConfigService;
import com.ruoyi.web.modules.account.constant.MemberConstants;
import com.ruoyi.web.modules.account.domain.BizMember;
import com.ruoyi.web.modules.account.domain.BizMemberAddress;
import com.ruoyi.web.modules.account.facade.IMemberFacade;
import com.ruoyi.web.modules.account.mapper.BizMemberAddressMapper;
import com.ruoyi.web.modules.cart.constant.CartConstants;
import com.ruoyi.web.modules.cart.mapper.CartAppMapper;
import com.ruoyi.web.modules.freight.constant.FreightTemplateConstants;
import com.ruoyi.web.modules.goods.domain.BizGoods;
import com.ruoyi.web.modules.goods.domain.BizGoodsFreight;
import com.ruoyi.web.modules.goods.facade.IGoodsPurchaseFacade;
import com.ruoyi.web.modules.goods.mapper.BizGoodsAttrMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsFreightMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsServiceSnapshotMapper;
import com.ruoyi.web.modules.goods.vo.GoodsPurchaseVO;
import com.ruoyi.web.modules.order.constant.CheckoutConstants;
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.dto.CheckoutLineDTO;
import com.ruoyi.web.modules.order.dto.CheckoutPreviewDTO;
import com.ruoyi.web.modules.order.dto.CheckoutSubmitDTO;
import com.ruoyi.web.modules.order.mapper.BizOrderItemMapper;
import com.ruoyi.web.modules.order.mapper.BizOrderMapper;
import com.ruoyi.web.modules.order.service.impl.CheckoutAppServiceImpl;
import com.ruoyi.web.modules.order.support.CheckoutSupport;
import com.ruoyi.web.modules.order.vo.CheckoutPreviewVO;
import com.ruoyi.web.modules.order.vo.CheckoutSubmitResultVO;
import com.ruoyi.web.modules.store.domain.BizShop;
import com.ruoyi.web.modules.store.mapper.BizShopMapper;

/**
 * 确认订单 · 立即购买（单商品 BUY_NOW）Service 测试
 */
@ExtendWith(MockitoExtension.class)
class CheckoutBuyNowAppServiceImplTest
{
    private static final Long MEMBER_ID = 200L;

    private static final Long GOODS_ID = 1L;

    private static final Long SHOP_ID = 101L;

    @Mock
    private IMemberFacade memberFacade;

    @Mock
    private IGoodsPurchaseFacade purchaseFacade;

    @Mock
    private CartAppMapper cartAppMapper;

    @Mock
    private BizGoodsMapper goodsMapper;

    @Mock
    private BizGoodsAttrMapper goodsAttrMapper;

    @Mock
    private BizGoodsFreightMapper goodsFreightMapper;

    @Mock
    private BizGoodsServiceSnapshotMapper snapshotMapper;

    @Mock
    private BizMemberAddressMapper addressMapper;

    @Mock
    private BizShopMapper shopMapper;

    @Mock
    private BizOrderMapper orderMapper;

    @Mock
    private BizOrderItemMapper orderItemMapper;

    @Mock
    private CheckoutSupport checkoutSupport;

    @Mock
    private ISysConfigService configService;

    @InjectMocks
    private CheckoutAppServiceImpl checkoutAppService;

    @Test
    void preview_buyNowSingleItemSuccess()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        when(goodsMapper.selectById(GOODS_ID)).thenReturn(goods());
        when(purchaseFacade.canPurchase(GOODS_ID)).thenReturn(allowed());
        BizMemberAddress address = address(5L);
        when(addressMapper.selectById(5L, MEMBER_ID)).thenReturn(address);
        when(shopMapper.selectById(SHOP_ID)).thenReturn(shop());
        mockFixedFreight(GOODS_ID, new BigDecimal("8.00"));
        when(checkoutSupport.sumGoodsAmount(anyList())).thenReturn(new BigDecimal("30.00"));
        when(checkoutSupport.resolveProvinceId(address)).thenReturn(11);
        when(checkoutSupport.calcFreight(eq(SHOP_ID), anyList(), eq(11), any())).thenReturn(new BigDecimal("8.00"));
        when(checkoutSupport.buildFreightDesc(eq(SHOP_ID), anyList(), any(), any())).thenReturn("运费 ¥8.00");
        when(checkoutSupport.buildServiceDesc(any())).thenReturn("7天无理由");
        when(checkoutSupport.buildLineFreightDesc(any(), eq(SHOP_ID))).thenReturn("运费 ¥8.00");
        when(snapshotMapper.selectVisibleByGoodsId(GOODS_ID)).thenReturn(Collections.emptyList());

        CheckoutPreviewVO vo = checkoutAppService.preview(MEMBER_ID, buyNowPreviewDto(3, 5L));

        assertEquals(1, vo.getItems().size());
        assertNull(vo.getItems().get(0).getCartItemId());
        assertEquals(0, vo.getGoodsAmount().compareTo(new BigDecimal("30.00")));
        assertEquals(0, vo.getFreightAmount().compareTo(new BigDecimal("8.00")));
        assertEquals(0, vo.getPayAmount().compareTo(new BigDecimal("38.00")));
        verify(cartAppMapper, never()).selectItemRowsByIdsAndMember(any(), anyList());
    }

    @Test
    void preview_buyNowMultipleItemsRejected()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);

        CheckoutPreviewDTO dto = new CheckoutPreviewDTO();
        dto.setSource(CheckoutConstants.SOURCE_BUY_NOW);
        CheckoutLineDTO line1 = new CheckoutLineDTO();
        line1.setGoodsId(GOODS_ID);
        line1.setQuantity(1);
        CheckoutLineDTO line2 = new CheckoutLineDTO();
        line2.setGoodsId(2L);
        line2.setQuantity(1);
        dto.setItems(Arrays.asList(line1, line2));

        ServiceException ex = assertThrows(ServiceException.class, () -> checkoutAppService.preview(MEMBER_ID, dto));
        assertEquals(CheckoutConstants.MSG_BUY_NOW_SINGLE_ONLY, ex.getMessage());
    }

    @Test
    void preview_buyNowCartItemIdsForbidden()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);

        CheckoutPreviewDTO dto = buyNowPreviewDto(1, null);
        dto.setCartItemIds(Collections.singletonList(10L));

        ServiceException ex = assertThrows(ServiceException.class, () -> checkoutAppService.preview(MEMBER_ID, dto));
        assertEquals(CheckoutConstants.MSG_BUY_NOW_CART_FORBIDDEN, ex.getMessage());
    }

    @Test
    void preview_buyNowNoAddressReturnsNullAddress()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        when(goodsMapper.selectById(GOODS_ID)).thenReturn(goods());
        when(purchaseFacade.canPurchase(GOODS_ID)).thenReturn(allowed());
        when(addressMapper.selectByMemberId(MEMBER_ID)).thenReturn(Collections.emptyList());
        when(shopMapper.selectById(SHOP_ID)).thenReturn(shop());
        mockFixedFreight(GOODS_ID, new BigDecimal("8.00"));
        when(checkoutSupport.sumGoodsAmount(anyList())).thenReturn(new BigDecimal("10.00"));
        when(checkoutSupport.resolveProvinceId(null)).thenReturn(null);
        when(checkoutSupport.calcFreight(eq(SHOP_ID), anyList(), eq(null), any())).thenReturn(new BigDecimal("8.00"));
        when(checkoutSupport.buildFreightDesc(any(), anyList(), any(), any())).thenReturn("运费 ¥8.00");
        when(checkoutSupport.buildServiceDesc(any())).thenReturn("");
        when(checkoutSupport.buildLineFreightDesc(any(), eq(SHOP_ID))).thenReturn("运费 ¥8.00");
        when(snapshotMapper.selectVisibleByGoodsId(GOODS_ID)).thenReturn(Collections.emptyList());

        CheckoutPreviewVO vo = checkoutAppService.preview(MEMBER_ID, buyNowPreviewDto(1, null));

        assertNull(vo.getAddress());
    }

    @Test
    void submit_buyNowRequiresAddress()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        when(goodsMapper.selectById(GOODS_ID)).thenReturn(goods());
        when(purchaseFacade.canPurchase(GOODS_ID)).thenReturn(allowed());
        when(addressMapper.selectById(5L, MEMBER_ID)).thenReturn(null);

        CheckoutSubmitDTO dto = buyNowSubmitDto(1, 5L);

        ServiceException ex = assertThrows(ServiceException.class, () -> checkoutAppService.submit(MEMBER_ID, dto));
        assertEquals(CheckoutConstants.MSG_ADDRESS_NOT_FOUND, ex.getMessage());
        verify(orderMapper, never()).insert(any());
    }

    @Test
    void submit_buyNowCreatesSingleItemWithoutCartId()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        when(memberFacade.getMember(MEMBER_ID)).thenReturn(member());
        when(goodsMapper.selectById(GOODS_ID)).thenReturn(goods());
        when(purchaseFacade.canPurchase(GOODS_ID)).thenReturn(allowed());
        BizMemberAddress address = address(5L);
        when(addressMapper.selectById(5L, MEMBER_ID)).thenReturn(address);
        when(shopMapper.selectById(SHOP_ID)).thenReturn(shop());
        mockFixedFreight(GOODS_ID, new BigDecimal("8.00"));
        when(checkoutSupport.sumGoodsAmount(anyList())).thenReturn(new BigDecimal("10.00"));
        when(checkoutSupport.resolveProvinceId(address)).thenReturn(11);
        when(checkoutSupport.calcFreight(eq(SHOP_ID), anyList(), eq(11), any())).thenReturn(new BigDecimal("8.00"));
        when(checkoutSupport.buildFreightDesc(any(), anyList(), any(), any())).thenReturn("运费 ¥8.00");
        when(checkoutSupport.buildServiceDesc(any())).thenReturn("");
        when(checkoutSupport.buildLineFreightDesc(any(), eq(SHOP_ID))).thenReturn("运费 ¥8.00");
        when(checkoutSupport.buildConsigneeAddress(address)).thenReturn("广东省深圳市南山区 科技园");
        when(snapshotMapper.selectVisibleByGoodsId(GOODS_ID)).thenReturn(Collections.emptyList());
        when(checkoutSupport.nextOrderNo(any())).thenReturn("O20260526000001");
        when(configService.selectConfigByKey(OrderConstants.CONFIG_PAY_TIMEOUT_MINUTES)).thenReturn("1440");
        when(orderMapper.insert(any(BizOrder.class))).thenAnswer(invocation -> {
            BizOrder order = invocation.getArgument(0);
            order.setOrderId(900L);
            return 1;
        });

        CheckoutSubmitResultVO result = checkoutAppService.submit(MEMBER_ID, buyNowSubmitDto(1, 5L));

        assertEquals(Long.valueOf(900L), result.getOrderId());
        assertEquals("O20260526000001", result.getOrderNo());
        ArgumentCaptor<List<BizOrderItem>> captor = ArgumentCaptor.forClass(List.class);
        verify(orderItemMapper).batchInsert(captor.capture());
        assertEquals(1, captor.getValue().size());
        assertNull(captor.getValue().get(0).getCartItemId());
        assertEquals("", captor.getValue().get(0).getSpecKey());
        verify(goodsMapper, never()).deductStock(any(), any());
    }

    @Test
    void submit_buyNowOffShelfRejected()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        when(goodsMapper.selectById(GOODS_ID)).thenReturn(goods());
        GoodsPurchaseVO denied = new GoodsPurchaseVO();
        denied.setAllowed(false);
        denied.setReason(CartConstants.MSG_INVALID_OFF_SHELF);
        when(purchaseFacade.canPurchase(GOODS_ID)).thenReturn(denied);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> checkoutAppService.submit(MEMBER_ID, buyNowSubmitDto(1, 5L)));
        assertEquals(CartConstants.MSG_INVALID_OFF_SHELF, ex.getMessage());
        verify(orderMapper, never()).insert(any());
    }

    private CheckoutPreviewDTO buyNowPreviewDto(int quantity, Long addressId)
    {
        CheckoutPreviewDTO dto = new CheckoutPreviewDTO();
        dto.setSource(CheckoutConstants.SOURCE_BUY_NOW);
        dto.setAddressId(addressId);
        CheckoutLineDTO line = new CheckoutLineDTO();
        line.setGoodsId(GOODS_ID);
        line.setQuantity(quantity);
        line.setSpecText("默认");
        dto.setItems(Collections.singletonList(line));
        return dto;
    }

    private CheckoutSubmitDTO buyNowSubmitDto(int quantity, Long addressId)
    {
        CheckoutSubmitDTO dto = new CheckoutSubmitDTO();
        dto.setSource(CheckoutConstants.SOURCE_BUY_NOW);
        dto.setAddressId(addressId);
        dto.setPayType(OrderConstants.PAY_TYPE_WECHAT);
        CheckoutLineDTO line = new CheckoutLineDTO();
        line.setGoodsId(GOODS_ID);
        line.setQuantity(quantity);
        line.setSpecText("默认");
        line.setBuyerRemark("请尽快发货");
        dto.setItems(Collections.singletonList(line));
        return dto;
    }

    private void mockFixedFreight(Long goodsId, BigDecimal amount)
    {
        BizGoodsFreight freight = new BizGoodsFreight();
        freight.setFreightType(FreightTemplateConstants.FREIGHT_TYPE_FIXED);
        freight.setFixedAmount(amount);
        when(goodsFreightMapper.selectByGoodsId(goodsId)).thenReturn(freight);
    }

    private GoodsPurchaseVO allowed()
    {
        GoodsPurchaseVO vo = new GoodsPurchaseVO();
        vo.setAllowed(true);
        return vo;
    }

    private BizGoods goods()
    {
        BizGoods goods = new BizGoods();
        goods.setGoodsId(GOODS_ID);
        goods.setShopId(SHOP_ID);
        goods.setGoodsName("测试商品");
        goods.setMainPic("/pic.jpg");
        goods.setSalePrice(new BigDecimal("10.00"));
        goods.setStock(99);
        return goods;
    }

    private BizMemberAddress address(Long id)
    {
        BizMemberAddress address = new BizMemberAddress();
        address.setAddressId(id);
        address.setConsigneeName("张三");
        address.setMobile("13800000000");
        address.setRegionName("广东省深圳市南山区");
        address.setDetailAddress("科技园");
        address.setRegionCode("440305");
        address.setIsDefault(MemberConstants.DEFAULT_YES);
        return address;
    }

    private BizShop shop()
    {
        BizShop shop = new BizShop();
        shop.setShopId(SHOP_ID);
        shop.setMerchantId(50L);
        shop.setShopName("测试店");
        shop.setShopStatus("0");
        return shop;
    }

    private BizMember member()
    {
        BizMember member = new BizMember();
        member.setMemberId(MEMBER_ID);
        member.setNickName("买家");
        return member;
    }
}
