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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
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.merchant.facade.IMerchantFacade;
import com.ruoyi.web.modules.merchant.facade.IMerchantShopFacade;
import com.ruoyi.web.modules.merchant.mapper.BizMerchantMapper;
import com.ruoyi.web.modules.merchant.vo.OpenShopCheckVO;
import com.ruoyi.web.modules.store.constant.ShopConstants;
import com.ruoyi.web.modules.store.domain.BizMerchantAccount;
import com.ruoyi.web.modules.store.domain.BizShop;
import com.ruoyi.web.modules.store.dto.SellerShopProfileDTO;
import com.ruoyi.web.modules.store.dto.ShopCreateDTO;
import com.ruoyi.web.modules.store.dto.ShopUpdateDTO;
import com.ruoyi.web.modules.store.facade.IGoodsShopFacade;
import com.ruoyi.web.modules.store.facade.IShopGlobalConfigFacade;
import com.ruoyi.web.modules.store.facade.IShopOrderFacade;
import com.ruoyi.web.modules.store.mapper.BizMerchantAccountMapper;
import com.ruoyi.web.modules.store.mapper.BizShopMapper;
import com.ruoyi.web.modules.store.service.impl.ShopServiceImpl;
import com.ruoyi.web.modules.store.vo.SellerShopProfileVO;
import com.ruoyi.web.modules.store.vo.SellerShopEmployeeStatsVO;
import com.ruoyi.web.modules.store.vo.ShopEmployeeCountRow;

@ExtendWith(MockitoExtension.class)
class ShopServiceImplTest
{
    @Mock
    private BizShopMapper shopMapper;

    @Mock
    private BizMerchantAccountMapper accountMapper;

    @Mock
    private BizMerchantMapper merchantMapper;

    @Mock
    private IMerchantFacade merchantFacade;

    @Mock
    private IMerchantShopFacade merchantShopFacade;

    @Mock
    private IGoodsShopFacade goodsShopFacade;

    @Mock
    private IShopOrderFacade shopOrderFacade;

    @Mock
    private IShopGlobalConfigFacade shopGlobalConfigFacade;

    @InjectMocks
    private ShopServiceImpl shopService;

    @Test
    void createShop_withMerchantAccount_insertsShopOnly()
    {
        ShopCreateDTO dto = buildCreateDto();
        OpenShopCheckVO allowed = new OpenShopCheckVO();
        allowed.setAllowed(true);
        BizMerchantAccount account = new BizMerchantAccount();
        account.setMerchantId(1L);
        account.setAccountId(100L);
        when(merchantFacade.checkOpenShop(1L)).thenReturn(allowed);
        when(shopMapper.countByShopName(eq("测试店"), isNull())).thenReturn(0);
        when(accountMapper.selectByMerchantId(1L)).thenReturn(account);
        when(shopMapper.insert(any())).thenAnswer(inv -> {
            BizShop s = inv.getArgument(0);
            s.setShopId(10L);
            return 1;
        });

        Long shopId = shopService.createShop(dto, "admin");

        assertEquals(10L, shopId);
        verify(accountMapper, never()).insert(any());
        verify(shopMapper).insert(any(BizShop.class));
        verify(merchantShopFacade).incrementShopCount(1L);
    }

    @Test
    void createShop_withoutMerchantAccount_rejected()
    {
        ShopCreateDTO dto = buildCreateDto();
        OpenShopCheckVO allowed = new OpenShopCheckVO();
        allowed.setAllowed(true);
        when(merchantFacade.checkOpenShop(1L)).thenReturn(allowed);
        when(shopMapper.countByShopName(eq("测试店"), isNull())).thenReturn(0);
        when(accountMapper.selectByMerchantId(1L)).thenReturn(null);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> shopService.createShop(dto, "admin"));
        assertEquals(ShopConstants.MSG_NO_MERCHANT_ACCOUNT, ex.getMessage());
        verify(shopMapper, never()).insert(any());
    }

    @Test
    void createShop_optionalPhoneAndStatus_allowed()
    {
        ShopCreateDTO dto = buildCreateDto();
        dto.setShopPhone(null);
        dto.setShopStatus(null);
        OpenShopCheckVO allowed = new OpenShopCheckVO();
        allowed.setAllowed(true);
        BizMerchantAccount account = new BizMerchantAccount();
        when(merchantFacade.checkOpenShop(1L)).thenReturn(allowed);
        when(shopMapper.countByShopName(any(), isNull())).thenReturn(0);
        when(accountMapper.selectByMerchantId(1L)).thenReturn(account);
        when(shopMapper.insert(any())).thenAnswer(inv -> {
            BizShop s = inv.getArgument(0);
            s.setShopId(11L);
            assertEquals(ShopConstants.STATUS_OPEN, s.getShopStatus());
            return 1;
        });

        shopService.createShop(dto, "admin");

        verify(shopMapper).insert(any(BizShop.class));
    }

    @Test
    void createShop_duplicateShopName_rejected()
    {
        ShopCreateDTO dto = buildCreateDto();
        OpenShopCheckVO allowed = new OpenShopCheckVO();
        allowed.setAllowed(true);
        when(merchantFacade.checkOpenShop(1L)).thenReturn(allowed);
        when(shopMapper.countByShopName(eq("测试店"), isNull())).thenReturn(1);

        assertThrows(ServiceException.class, () -> shopService.createShop(dto, "admin"));
    }

    @Test
    void createShop_openShopNotAllowed_rejected()
    {
        ShopCreateDTO dto = buildCreateDto();
        OpenShopCheckVO denied = new OpenShopCheckVO();
        denied.setAllowed(false);
        denied.setReason("经营信息未完善");
        when(merchantFacade.checkOpenShop(1L)).thenReturn(denied);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> shopService.createShop(dto, "admin"));
        assertEquals("经营信息未完善", ex.getMessage());
        verify(shopMapper, never()).insert(any());
    }

    @Test
    void updateSellerShopProfile_withoutPhone_allowed()
    {
        when(shopMapper.selectById(1L)).thenReturn(openShop(1L));
        when(shopMapper.countByShopName(eq("新店名"), eq(1L))).thenReturn(0);
        SellerShopProfileDTO dto = new SellerShopProfileDTO();
        dto.setShopName("新店名");
        dto.setShopAvatar("/new.jpg");
        dto.setShopDesc("描述");

        shopService.updateSellerShopProfile(1L, dto, "seller");

        verify(shopMapper).update(any(BizShop.class));
    }

    @Test
    void selectSellerShopProfile_joinMerchantAndMaskContactPhone()
    {
        when(shopMapper.selectById(101L)).thenReturn(openShop(101L));
        SellerShopProfileVO profile = new SellerShopProfileVO();
        profile.setShopId(101L);
        profile.setShopName("张三农资店");
        profile.setContactName("张三");
        profile.setContactPhone("13800138000");
        when(shopMapper.selectSellerProfileById(101L)).thenReturn(profile);

        SellerShopProfileVO vo = shopService.selectSellerShopProfile(101L);

        assertEquals("张三农资店", vo.getShopName());
        assertEquals("张三", vo.getContactName());
        assertEquals("138****8000", vo.getContactPhone());
    }

    @Test
    void selectSellerShopProfile_joinMissing_rejected()
    {
        when(shopMapper.selectById(101L)).thenReturn(openShop(101L));
        when(shopMapper.selectSellerProfileById(101L)).thenReturn(null);

        assertThrows(ServiceException.class, () -> shopService.selectSellerShopProfile(101L));
    }

    @Test
    void updateSellerShopProfile_duplicateName_rejected()
    {
        when(shopMapper.selectById(1L)).thenReturn(openShop(1L));
        when(shopMapper.countByShopName(eq("重复名"), eq(1L))).thenReturn(1);
        SellerShopProfileDTO dto = new SellerShopProfileDTO();
        dto.setShopName("重复名");
        dto.setShopAvatar("/a.jpg");

        ServiceException ex = assertThrows(ServiceException.class,
                () -> shopService.updateSellerShopProfile(1L, dto, "seller"));
        assertEquals(ShopConstants.MSG_SHOP_NAME_EXISTS, ex.getMessage());
        verify(shopMapper, never()).update(any());
    }

    @Test
    void updateSellerShopProfile_invalidPhone_rejected()
    {
        when(shopMapper.selectById(1L)).thenReturn(openShop(1L));
        SellerShopProfileDTO dto = new SellerShopProfileDTO();
        dto.setShopName("新店名");
        dto.setShopAvatar("/a.jpg");
        dto.setShopPhone("bad-phone");

        assertThrows(ServiceException.class,
                () -> shopService.updateSellerShopProfile(1L, dto, "seller"));
        verify(shopMapper, never()).update(any());
    }

    @Test
    void updateSellerShopProfile_descTooLong_rejected()
    {
        when(shopMapper.selectById(1L)).thenReturn(openShop(1L));
        SellerShopProfileDTO dto = new SellerShopProfileDTO();
        dto.setShopName("新店名");
        dto.setShopAvatar("/a.jpg");
        dto.setShopDesc(StringUtils.repeat("x", 1001));

        assertThrows(ServiceException.class,
                () -> shopService.updateSellerShopProfile(1L, dto, "seller"));
        verify(shopMapper, never()).update(any());
    }

    @Test
    void selectSellerEmployeeStats_returnsShopAndQuotaCounts()
    {
        BizShop shop = openShop(101L);
        shop.setShopName("张三农资店");
        when(shopMapper.selectById(101L)).thenReturn(shop);
        ShopEmployeeCountRow countRow = new ShopEmployeeCountRow();
        countRow.setTotalCount(3);
        countRow.setDisabledCount(1);
        when(accountMapper.countEmployeesByShopId(1L, 101L)).thenReturn(countRow);
        when(accountMapper.countEmployeesByMerchantId(1L)).thenReturn(4);
        when(shopGlobalConfigFacade.getMaxSubAdmin()).thenReturn(5);

        SellerShopEmployeeStatsVO stats = shopService.selectSellerEmployeeStats(101L);

        assertEquals(101L, stats.getShopId());
        assertEquals("张三农资店", stats.getShopName());
        assertEquals(3, stats.getTotalCount());
        assertEquals(1, stats.getDisabledCount());
        assertEquals(4, stats.getUsedCount());
        assertEquals(5, stats.getMaxCount());
    }

    @Test
    void selectSellerEmployeeStats_defaultMaxWhenFacadeNull()
    {
        when(shopMapper.selectById(101L)).thenReturn(openShop(101L));
        when(accountMapper.countEmployeesByShopId(1L, 101L)).thenReturn(new ShopEmployeeCountRow());
        when(accountMapper.countEmployeesByMerchantId(1L)).thenReturn(0);
        when(shopGlobalConfigFacade.getMaxSubAdmin()).thenReturn(null);

        SellerShopEmployeeStatsVO stats = shopService.selectSellerEmployeeStats(101L);

        assertEquals(ShopConstants.DEFAULT_MAX_SUB_ADMIN, stats.getMaxCount());
        assertNotNull(stats);
    }

    @Test
    void updateShop_closeWithoutConfirm_returnsNeedConfirm()
    {
        BizShop existing = openShop(1L);
        when(shopMapper.selectById(1L)).thenReturn(existing);
        ShopUpdateDTO dto = new ShopUpdateDTO();
        dto.setShopId(1L);
        dto.setShopStatus(ShopConstants.STATUS_CLOSED);
        dto.setConfirm(false);

        Map<String, Object> data = shopService.updateShop(dto, "admin");

        assertTrue((Boolean) data.get("needConfirm"));
        verify(shopMapper, never()).update(any());
    }

    @Test
    void updateShop_closeWithConfirm_persists()
    {
        BizShop existing = openShop(1L);
        when(shopMapper.selectById(1L)).thenReturn(existing);
        ShopUpdateDTO dto = new ShopUpdateDTO();
        dto.setShopId(1L);
        dto.setShopStatus(ShopConstants.STATUS_CLOSED);
        dto.setConfirm(true);

        Map<String, Object> data = shopService.updateShop(dto, "admin");

        assertFalse((Boolean) data.get("needConfirm"));
        verify(shopMapper).update(any(BizShop.class));
    }

    @Test
    void updateShop_reopen_noConfirmRequired()
    {
        BizShop existing = openShop(1L);
        existing.setShopStatus(ShopConstants.STATUS_CLOSED);
        when(shopMapper.selectById(1L)).thenReturn(existing);
        ShopUpdateDTO dto = new ShopUpdateDTO();
        dto.setShopId(1L);
        dto.setShopStatus(ShopConstants.STATUS_OPEN);

        shopService.updateShop(dto, "admin");

        verify(shopMapper).update(any(BizShop.class));
    }

    private ShopCreateDTO buildCreateDto()
    {
        ShopCreateDTO dto = new ShopCreateDTO();
        dto.setMerchantId(1L);
        dto.setShopName("测试店");
        dto.setShopAvatar("/profile/upload/a.jpg");
        dto.setShopPhone("010-88886666");
        return dto;
    }

    private BizShop openShop(Long shopId)
    {
        BizShop shop = new BizShop();
        shop.setShopId(shopId);
        shop.setMerchantId(1L);
        shop.setShopStatus(ShopConstants.STATUS_OPEN);
        shop.setShopName("店");
        shop.setShopAvatar("/a.jpg");
        shop.setShopPhone("010-88886666");
        return shop;
    }
}
