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

import static org.junit.jupiter.api.Assertions.assertEquals;
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.verify;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
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 com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.modules.category.constant.CategoryConstants;
import com.ruoyi.web.modules.category.domain.BizGoodsCategory;
import com.ruoyi.web.modules.category.facade.ICategoryFacade;
import com.ruoyi.web.modules.category.mapper.BizGoodsCategoryMapper;
import com.ruoyi.web.modules.goodsservice.facade.IGoodsServiceFacade;
import com.ruoyi.web.modules.goodsservice.mapper.BizGoodsServiceMapper;
import com.ruoyi.web.modules.goods.constant.GoodsConstants;
import com.ruoyi.web.modules.goods.domain.BizGoods;
import com.ruoyi.web.modules.goods.dto.GoodsAuditDTO;
import com.ruoyi.web.modules.goods.dto.GoodsOffShelfDTO;
import com.ruoyi.web.modules.goods.dto.GoodsSaveDTO;
import com.ruoyi.web.modules.goods.exception.GoodsBatchOperationException;
import com.ruoyi.web.modules.goods.mapper.BizGoodsMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsServiceSnapshotMapper;
import com.ruoyi.web.modules.goods.service.impl.GoodsServiceImpl;
import com.ruoyi.web.modules.goods.support.GoodsSnGenerator;
import com.ruoyi.web.modules.merchant.mapper.BizMerchantMapper;
import com.ruoyi.web.modules.store.constant.ShopConstants;
import com.ruoyi.web.modules.store.domain.BizShop;
import com.ruoyi.web.modules.store.facade.IShopGlobalConfigFacade;
import com.ruoyi.web.modules.store.mapper.BizShopMapper;

@ExtendWith(MockitoExtension.class)
class GoodsServiceImplTest
{
    @Mock
    private BizGoodsMapper goodsMapper;

    @Mock
    private BizGoodsServiceSnapshotMapper snapshotMapper;

    @Mock
    private BizGoodsCategoryMapper categoryMapper;

    @Mock
    private BizShopMapper shopMapper;

    @Mock
    private BizGoodsServiceMapper goodsServiceMapper;

    @Mock
    private BizMerchantMapper merchantMapper;

    @Mock
    private ICategoryFacade categoryFacade;

    @Mock
    private IGoodsServiceFacade goodsServiceFacade;

    @Mock
    private GoodsSnGenerator goodsSnGenerator;

    @Mock
    private IShopGlobalConfigFacade shopGlobalConfigFacade;

    @InjectMocks
    private GoodsServiceImpl goodsService;

    @Test
    void submit_fromDraft_needAudit_becomesPending()
    {
        BizGoods goods = draftGoods(1L, 10L);
        when(goodsMapper.selectByIdAndShop(1L, 10L)).thenReturn(goods);
        BizShop shop = new BizShop();
        when(shopMapper.selectById(10L)).thenReturn(shop);
        when(shopGlobalConfigFacade.getDefaultAuditPass()).thenReturn(ShopConstants.AUDIT_PASS_NO);

        Map<String, Object> data = goodsService.submitGoods(10L, 1L, "seller");

        assertEquals(GoodsConstants.STATUS_PENDING, data.get("goodsStatus"));
        verify(goodsMapper).updateSubmitStatus(eq(1L), eq(GoodsConstants.STATUS_PENDING), any(Date.class),
                eq(true), eq("seller"));
    }

    @Test
    void submit_fromDraft_autoPass_becomesOnSale()
    {
        BizGoods goods = draftGoods(2L, 10L);
        when(goodsMapper.selectByIdAndShop(2L, 10L)).thenReturn(goods);
        BizShop shop = new BizShop();
        when(shopMapper.selectById(10L)).thenReturn(shop);
        when(shopGlobalConfigFacade.getDefaultAuditPass()).thenReturn(ShopConstants.AUDIT_PASS_YES);

        Map<String, Object> data = goodsService.submitGoods(10L, 2L, "seller");

        assertEquals(GoodsConstants.STATUS_ON_SALE, data.get("goodsStatus"));
    }

    @Test
    void submit_fromOnSale_rejected()
    {
        BizGoods goods = draftGoods(3L, 10L);
        goods.setGoodsStatus(GoodsConstants.STATUS_ON_SALE);
        when(goodsMapper.selectByIdAndShop(3L, 10L)).thenReturn(goods);

        assertThrows(ServiceException.class, () -> goodsService.submitGoods(10L, 3L, "seller"));
    }

    @Test
    void submit_resubmit_keepsSubmitTime()
    {
        Date t1 = new Date(1_000_000L);
        BizGoods goods = draftGoods(4L, 10L);
        goods.setGoodsStatus(GoodsConstants.STATUS_OFF_SHELF);
        goods.setSubmitTime(t1);
        when(goodsMapper.selectByIdAndShop(4L, 10L)).thenReturn(goods);
        BizShop shop = new BizShop();
        when(shopMapper.selectById(10L)).thenReturn(shop);
        when(shopGlobalConfigFacade.getDefaultAuditPass()).thenReturn(ShopConstants.AUDIT_PASS_NO);

        goodsService.submitGoods(10L, 4L, "seller");

        verify(goodsMapper).updateSubmitStatus(eq(4L), eq(GoodsConstants.STATUS_PENDING), eq(t1), eq(false),
                eq("seller"));
    }

    @Test
    void audit_pass_updatesOnSale()
    {
        BizGoods pending = draftGoods(11L, 10L);
        pending.setGoodsStatus(GoodsConstants.STATUS_PENDING);
        when(goodsMapper.selectById(11L)).thenReturn(pending);
        when(goodsMapper.updateAuditPass(11L, "admin")).thenReturn(1);

        GoodsAuditDTO dto = new GoodsAuditDTO();
        dto.setGoodsIds(Collections.singletonList(11L));
        dto.setPass(true);
        goodsService.auditGoods(dto, "admin");

        verify(goodsMapper).updateAuditPass(11L, "admin");
    }

    @Test
    void audit_reject_writesReason()
    {
        BizGoods pending = draftGoods(12L, 10L);
        pending.setGoodsStatus(GoodsConstants.STATUS_PENDING);
        when(goodsMapper.selectById(12L)).thenReturn(pending);
        when(goodsMapper.updateAuditReject(12L, "资料不全", "admin")).thenReturn(1);

        GoodsAuditDTO dto = new GoodsAuditDTO();
        dto.setGoodsIds(Collections.singletonList(12L));
        dto.setPass(false);
        dto.setRejectReason("资料不全");
        goodsService.auditGoods(dto, "admin");

        verify(goodsMapper).updateAuditReject(12L, "资料不全", "admin");
    }

    @Test
    void audit_emptyGoodsIds_fails()
    {
        GoodsAuditDTO dto = new GoodsAuditDTO();
        dto.setGoodsIds(Collections.emptyList());
        dto.setPass(true);

        assertThrows(ServiceException.class, () -> goodsService.auditGoods(dto, "admin"));
    }

    @Test
    void audit_goodsNotFound_batchFail()
    {
        when(goodsMapper.selectById(99L)).thenReturn(null);

        GoodsAuditDTO dto = new GoodsAuditDTO();
        dto.setGoodsIds(Collections.singletonList(99L));
        dto.setPass(true);

        GoodsBatchOperationException ex = assertThrows(GoodsBatchOperationException.class,
                () -> goodsService.auditGoods(dto, "admin"));
        assertTrue(ex.getReasons().get(0).contains("商品不存在"));
    }

    @Test
    void offShelf_pending_fails()
    {
        BizGoods pending = draftGoods(13L, 10L);
        pending.setGoodsStatus(GoodsConstants.STATUS_PENDING);
        when(goodsMapper.selectById(13L)).thenReturn(pending);

        GoodsOffShelfDTO dto = new GoodsOffShelfDTO();
        dto.setGoodsIds(Collections.singletonList(13L));

        assertThrows(GoodsBatchOperationException.class, () -> goodsService.offShelfPlatform(dto, "admin"));
        verify(goodsMapper, never()).updateOffShelf(any(), any());
    }

    @Test
    void offShelf_batchMixed_fails()
    {
        BizGoods onSale = draftGoods(14L, 10L);
        onSale.setGoodsStatus(GoodsConstants.STATUS_ON_SALE);
        BizGoods off = draftGoods(15L, 10L);
        off.setGoodsStatus(GoodsConstants.STATUS_OFF_SHELF);
        when(goodsMapper.selectById(14L)).thenReturn(onSale);
        when(goodsMapper.selectById(15L)).thenReturn(off);

        GoodsOffShelfDTO dto = new GoodsOffShelfDTO();
        dto.setGoodsIds(Arrays.asList(14L, 15L));

        assertThrows(GoodsBatchOperationException.class, () -> goodsService.offShelfPlatform(dto, "admin"));
        verify(goodsMapper, never()).updateOffShelf(any(), any());
    }

    @Test
    void countPlatformPending_delegatesMapper()
    {
        when(goodsMapper.countPlatformPending()).thenReturn(7);
        assertEquals(7, goodsService.countPlatformPending());
    }

    @Test
    void selectPlatformDetail_draft_notFound()
    {
        BizGoods draft = draftGoods(16L, 10L);
        draft.setGoodsStatus(GoodsConstants.STATUS_DRAFT);
        when(goodsMapper.selectById(16L)).thenReturn(draft);

        assertThrows(ServiceException.class, () -> goodsService.selectPlatformDetail(16L));
    }

    @Test
    void audit_rejectWithoutReason_fails()
    {
        GoodsAuditDTO dto = new GoodsAuditDTO();
        dto.setGoodsIds(Collections.singletonList(1L));
        dto.setPass(false);

        assertThrows(ServiceException.class, () -> goodsService.auditGoods(dto, "admin"));
    }

    @Test
    void audit_batchWithNonPending_fails()
    {
        BizGoods pending = draftGoods(1L, 10L);
        pending.setGoodsStatus(GoodsConstants.STATUS_PENDING);
        BizGoods onSale = draftGoods(2L, 10L);
        onSale.setGoodsStatus(GoodsConstants.STATUS_ON_SALE);
        when(goodsMapper.selectById(1L)).thenReturn(pending);
        when(goodsMapper.selectById(2L)).thenReturn(onSale);

        GoodsAuditDTO dto = new GoodsAuditDTO();
        dto.setGoodsIds(Arrays.asList(1L, 2L));
        dto.setPass(true);

        assertThrows(GoodsBatchOperationException.class, () -> goodsService.auditGoods(dto, "admin"));
        verify(goodsMapper, never()).updateAuditPass(any(), any());
    }

    @Test
    void offShelf_onlyOnSale()
    {
        BizGoods goods = draftGoods(5L, 10L);
        goods.setGoodsStatus(GoodsConstants.STATUS_ON_SALE);
        when(goodsMapper.selectById(5L)).thenReturn(goods);
        when(goodsMapper.updateOffShelf(5L, "admin")).thenReturn(1);

        GoodsOffShelfDTO dto = new GoodsOffShelfDTO();
        dto.setGoodsIds(Collections.singletonList(5L));
        goodsService.offShelfPlatform(dto, "admin");

        verify(goodsMapper).updateOffShelf(5L, "admin");
    }

    @Test
    void update_rejectsGoodsStatusInBody()
    {
        GoodsSaveDTO dto = new GoodsSaveDTO();
        dto.setGoodsId(6L);
        dto.setGoodsStatus(GoodsConstants.STATUS_ON_SALE);

        assertThrows(ServiceException.class,
                () -> goodsService.updateSellerGoods(10L, dto, "seller"));
    }

    @Test
    void insert_generatesSnAndDraftStatus()
    {
        when(goodsSnGenerator.nextSn()).thenReturn("G20260526000001");
        BizGoodsCategory cat = new BizGoodsCategory();
        cat.setCategoryLevel(CategoryConstants.LEVEL_TWO);
        when(categoryMapper.selectById(100L, 10L)).thenReturn(cat);
        when(goodsMapper.insert(any())).thenAnswer(inv -> {
            BizGoods g = inv.getArgument(0);
            g.setGoodsId(99L);
            return 1;
        });

        GoodsSaveDTO dto = new GoodsSaveDTO();
        dto.setCategoryId(100L);
        dto.setGoodsName("复合肥");
        dto.setMainPic("/a.jpg");
        dto.setDetailContent("detail");
        dto.setSalePrice(new BigDecimal("10"));
        dto.setStock(5);

        Long id = goodsService.insertSellerGoods(10L, dto, "seller");

        assertEquals(99L, id);
        verify(goodsMapper).insert(any(BizGoods.class));
    }

    private BizGoods draftGoods(Long goodsId, Long shopId)
    {
        BizGoods goods = new BizGoods();
        goods.setGoodsId(goodsId);
        goods.setShopId(shopId);
        goods.setGoodsStatus(GoodsConstants.STATUS_DRAFT);
        goods.setGoodsSn("G20260526000001");
        return goods;
    }
}
