package com.cf.imes.module.system.service.funds.products;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.i18n.core.util.I18nUtils;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest;
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import com.cf.imes.module.system.dal.mysql.funds.products.ProductsDetailMapper;
import com.cf.imes.module.system.dal.mysql.funds.products.ProductsMapper;
import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum;
import com.cf.imes.module.system.enums.products.ProductStatusEnum;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import java.math.BigDecimal;
import java.util.List;

import static com.cf.imes.framework.test.core.util.AssertUtils.assertPojoEquals;
import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceException;
import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_DETAIL_DURATION_EXIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_DETAIL_NO_EXIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_DETAIL_NO_UPDATE;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_NOT_NOLIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_NO_EXIST;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * @author Gqr
 * @since 2025/10/14 13:48
 */
class ProductsDetailServiceImplTest extends BaseMockitoUnitTest {
    @InjectMocks
    private ProductsDetailServiceImpl productsDetailService;

    @Mock
    private ProductsDetailMapper productsDetailMapper;

    @Mock
    private ProductsMapper productsMapper;

    @Mock
    private I18nUtils i18nUtils;

    @BeforeAll
    static void init(){
        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), ProductsDetailDO.class);
    }

    @Test
    void testCreateProductDetails() {
        // mock下架状态的产品
        ProductsDO mockProduct = new ProductsDO();
        mockProduct.setId(123L);
        mockProduct.setDeleted(false);
        mockProduct.setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus());

        when(productsMapper.selectOne(any())).thenReturn(mockProduct);

        ProductDetailsSaveReqVO productDetailsSaveReqVO = randomPojo(ProductDetailsSaveReqVO.class, p -> {
            p.setDiscount(BigDecimal.valueOf(10));
            p.setDuration(ProductDurationUnitEnum.YEAR.getCode());
            p.setProductId(mockProduct.getId());
        });

        // 模拟 Mapper 插入时生成 ID
        doAnswer(invocation -> {
            ProductsDetailDO detail = invocation.getArgument(0);
            detail.setId(100L); // 模拟自增主键
            return null;
        }).when(productsDetailMapper).insert(any(ProductsDetailDO.class));

        // 调用
        Long id = productsDetailService.create(productDetailsSaveReqVO);

        // 断言
        assertEquals(100L,id);

        // 验证 Mapper 方法被调用一次
        verify(productsDetailMapper, times(1)).insert(argThat((ProductsDetailDO detail) ->
                detail.getDiscount().equals(productDetailsSaveReqVO.getDiscount())
                        && detail.getDuration().equals(productDetailsSaveReqVO.getDuration())
                        && detail.getProductId().equals(mockProduct.getId())
        ));
    }

    @Test
    void testCreateProductDetails_ProductNotExist() {
        ProductDetailsSaveReqVO productDetailsSaveReqVO = randomPojo(ProductDetailsSaveReqVO.class);
        when(productsMapper.selectOne(any())).thenReturn(null);

        assertServiceException(() -> productsDetailService.create(productDetailsSaveReqVO),
                PRODUCTS_NO_EXIST);

        ProductsDO mockProduct = new ProductsDO();
        mockProduct.setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus());
        when(productsMapper.selectOne(any())).thenReturn(mockProduct);

        assertServiceException(() -> productsDetailService.create(productDetailsSaveReqVO),
                PRODUCTS_DETAIL_NO_UPDATE);
    }

    @Test
    void testCheckProductDetails_duplicate() {
        // mock下架状态的产品
        ProductsDO mockProduct = new ProductsDO();
        mockProduct.setId(123L);
        mockProduct.setDeleted(false);
        mockProduct.setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus());

        when(productsMapper.selectOne(any())).thenReturn(mockProduct);

        // 模拟新增同样 duration + unit 的记录
        ProductDetailsSaveReqVO reqVO = new ProductDetailsSaveReqVO()
                .setId(999L)
                .setProductId(123L)
                .setDuration(1)
                .setDurationUnit(ProductDurationUnitEnum.MONTH.getCode());

        // mock重复存在
        when(productsDetailMapper.selectCountByDuration(reqVO.getProductId(), reqVO.getId(), reqVO.getDuration(), reqVO.getDurationUnit())).thenReturn(1);

        when(i18nUtils.getMessage(anyString())).thenReturn("月");
        assertServiceException(() -> productsDetailService.create(reqVO),
                PRODUCTS_DETAIL_DURATION_EXIST, reqVO.getDuration(), ProductDurationUnitEnum.getDesc(reqVO.getDurationUnit()));
    }

    @Test
    void updateProductDetails() {
        // mock下架状态的产品
        ProductsDO mockProduct = new ProductsDO();
        mockProduct.setId(123L);
        mockProduct.setDeleted(false);
        mockProduct.setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus());

        when(productsMapper.selectOne(any())).thenReturn(mockProduct);

        // mock exist
        ProductsDetailDO detail = new ProductsDetailDO();
        detail.setId(999L);
        detail.setProductId(mockProduct.getId());
        detail.setDuration(12);
        detail.setDurationUnit(1);
        detail.setDeleted(false);
        when(productsDetailMapper.selectOne(any(LambdaQueryWrapperX.class))).thenReturn(detail);

        // 构造请求
        ProductDetailsSaveReqVO reqVO = new ProductDetailsSaveReqVO()
                .setId(detail.getId())
                .setProductId(mockProduct.getId())
                .setDuration(6)
                .setDurationUnit(0);

        // 调用
        productsDetailService.update(reqVO);

        // 校验状态更新
        verify(productsDetailMapper).update(argThat((LambdaUpdateWrapper wrapper) ->
                wrapper.getSqlSegment().contains("id")
                        && wrapper.getSqlSet().contains("discount")
                        && wrapper.getSqlSet().contains("duration")
                        && wrapper.getSqlSet().contains("duration_unit")
        ));
    }

    @Test
    void deleteTest() {
        // mock下架状态的产品
        ProductsDO mockProduct = new ProductsDO();
        mockProduct.setId(123L);
        mockProduct.setDeleted(false);
        mockProduct.setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus());

        when(productsMapper.selectOne(any())).thenReturn(mockProduct);

        ProductsDetailDO detail = new ProductsDetailDO();
        detail.setId(999L);
        detail.setProductId(mockProduct.getId());
        detail.setDuration(12);
        detail.setDurationUnit(1);
        detail.setDeleted(false);
        when(productsDetailMapper.selectOne(any(LambdaQueryWrapperX.class))).thenReturn(detail);

        // 调用删除
        productsDetailService.delete(detail.getId());

        // 验证确实被删
        assertNull(productsDetailMapper.selectById(detail.getId()));
    }

    @Test
    void testGet_success() {
        // 插入一条数据
        ProductsDetailDO detail = new ProductsDetailDO();
        detail.setId(321L);
        detail.setDeleted(false);
        detail.setProductId(123L);
        when(productsDetailMapper.selectOne(any(LambdaQueryWrapperX.class))).thenReturn(detail);

        ProductsDetailDO result = productsDetailService.get(detail.getId());
        assertNotNull(result);
        assertEquals(321L, result.getId());
        assertEquals(123L, result.getProductId());
    }

    @Test
    void testGetPage_success() {
        ProductsDetailDO detail = randomPojo(ProductsDetailDO.class, o -> {
            o.setProductId(123L);
            o.setDuration(1);
            o.setDurationUnit(ProductDurationUnitEnum.YEAR.getCode());
            o.setDiscount(BigDecimal.valueOf(100));
            o.setDeleted(false);
        });

        // ============ 构造查询参数 ============
        ProductDetailsPageReqVO reqVO = new ProductDetailsPageReqVO();
        reqVO.setProductId(123L);
        reqVO.setDuration(1);
        reqVO.setDiscount(BigDecimal.valueOf(100));
        reqVO.setPageNo(1);
        reqVO.setPageSize(10);
        when(productsDetailMapper.selectPage(reqVO)).thenReturn(new PageResult<>(List.of(detail), 1L));

        // ============ 调用方法 ============
        PageResult<ProductsDetailDO> pageResult = productsDetailService.getPage(reqVO);

        // ============ 断言结果 ============
        assertEquals(1, pageResult.getTotal(), "分页结果数量应为1");
        assertEquals(1, pageResult.getList().size(), "分页列表长度应为1");
        assertPojoEquals(detail, pageResult.getList().get(0));
    }

    @Test
    void validateProductDetailsExists_idNull(){
        assertServiceException(() -> productsDetailService.validateProductDetailsExists(null), PRODUCTS_DETAIL_NO_EXIST);
    }

    @Test
    void validateProductDetailsExists_notExist(){
        assertServiceException(() -> productsDetailService.validateProductDetailsExists(1234567L), PRODUCTS_DETAIL_NO_EXIST);
    }


    @Test
    void testValidateProductExist_success() {
        // 模拟返回一个正常的产品
        ProductsDO product = new ProductsDO();
        product.setId(1L);
        product.setDeleted(false);
        product.setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus());

        when(productsMapper.selectOne(any())).thenReturn(product);

        ProductsDO result = productsDetailService.validateProductExist(1L);

        assertNotNull(result);
        assertEquals(1L, result.getId());
    }

    @Test
    void testValidateProductExist_notExist() {
        // 模拟数据库返回 null
        when(productsMapper.selectOne(any())).thenReturn(null);

        assertServiceException(() -> productsDetailService.validateProductExist(1L), PRODUCTS_NO_EXIST);
    }

    @Test
    void testValidateProductExist_statusNotUp() {
        ProductsDO product = new ProductsDO();
        product.setId(1L);
        product.setDeleted(false);
        product.setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus()); // 非 PRODUCT_UPDATES 状态

        when(productsMapper.selectOne(any())).thenReturn(product);

        assertServiceException(() -> productsDetailService.validateProductExist(1L), PRODUCTS_NOT_NOLIST);
    }

    @Test
    void testGetProductDetailOneYearDiscount_exists() {
        // 模拟数据库返回一年期明细
        ProductsDetailDO detailDO = new ProductsDetailDO();
        detailDO.setProductId(1L);
        detailDO.setDuration(1);
        detailDO.setDurationUnit(ProductDurationUnitEnum.YEAR.getCode());
        detailDO.setDiscount(BigDecimal.valueOf(85));
        when(productsDetailMapper.selectOne(any())).thenReturn(detailDO);

        BigDecimal discount = productsDetailService.getProductDetailOneYearDiscount(1L);
        assertEquals(BigDecimal.valueOf(85), discount);
    }

    @Test
    void testGetProductDetailOneYearDiscount_notExist() {
        BigDecimal discount = productsDetailService.getProductDetailOneYearDiscount(123L);
        assertEquals(BigDecimal.valueOf(100), discount);
    }
}
