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

import cn.hutool.core.collection.CollUtil;
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.exception.ServiceException;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.TotalSalesAmountRespVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.OrgEarliestPurchaseProductRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.SalesDetailRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.SalesDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.organ.vo.organ.OrganCreateProductPurchaseReqVO;
import com.cf.imes.module.system.controller.admin.statistics.vo.ManageSalesTotalStatisticRespVO;
import com.cf.imes.module.system.dal.dataobject.funds.productpromotion.ProductPromotionDO;
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.dataobject.funds.purchase.PurchaseRecordDO;
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
import com.cf.imes.module.system.dal.mysql.funds.purchase.PurchaseRecordMapper;
import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum;
import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum;
import com.cf.imes.module.system.enums.pay.PurchaseRecordStatusEnum;
import com.cf.imes.module.system.enums.pay.TradeTypeEnum;
import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties;
import com.cf.imes.module.system.service.funds.delay.ProductDelayService;
import com.cf.imes.module.system.service.funds.productpromotion.ProductPromotionService;
import com.cf.imes.module.system.service.funds.products.ProductsDetailService;
import com.cf.imes.module.system.service.organ.OrganService;
import com.cf.imes.framework.common.util.i18n.core.util.I18nUtils;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

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.ORG_PRODUCT_PURCHASE_EXIST_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORG_PRODUCT_PURCHASE_NOT_EXIST_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_PRICE_NULL_ERROR;
import static com.cf.imes.module.system.enums.pay.InvoiceStatusEnum.INVOICABLE;
import static com.cf.imes.module.system.enums.pay.InvoiceStatusEnum.INVOICINGSUCCESSFUL;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class PurchaseServiceImplTest extends BaseMockitoUnitTest {

    @InjectMocks
    private PurchaseServiceImpl purchaseService;

    @Mock
    private PurchaseRecordMapper purchaseRecordMapper;

    @Mock
    private OrganService organService;

    @Mock
    private I18nUtils i18nUtils;

    @Mock
    private ProductsDetailService productsDetailService;

    @Mock
    private ProductDelayService productDelayService;

    @Mock
    private ProductPromotionService productPromotionService;

    @Mock
    private MybatisIdProperties mybatisIdProperties;

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

    @Test
    void testCreateAndGetAndDeleteRecord() {
        // 准备参数
        PurchaseRecordDO invoiceRecordsDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1L);
            o.setProductId(100L);
            o.setCreateTime(LocalDateTime.now());
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setProductName("测试产品");
            o.setTotalAmount(new BigDecimal("200.00"));
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(INVOICABLE.getCode());
            o.setInitial(false);
            o.setOrganId(1L);
        });

        // when
        purchaseService.createRecord(invoiceRecordsDO);

        // then
        ArgumentCaptor<PurchaseRecordDO> captor =
                ArgumentCaptor.forClass(PurchaseRecordDO.class);

        verify(purchaseRecordMapper).insert(captor.capture());

        PurchaseRecordDO actual = captor.getValue();
        assertEquals(1L, actual.getOrganId());
        assertEquals(100L, actual.getProductId());
    }

    @Test
    void testGet(){
        PurchaseRecordDO invoiceRecordsDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1L);
            o.setProductId(100L);
            o.setTotalAmount(new BigDecimal("200.00"));
        });
        when(purchaseRecordMapper.selectById(anyLong())).thenReturn(invoiceRecordsDO);

        // 查询
        PurchaseRecordDO result = purchaseService.getRecord(1L);

        // 验证插入
        assertNotNull(result);
        assertEquals(invoiceRecordsDO.getProductName(), result.getProductName());
        assertEquals(invoiceRecordsDO.getTotalAmount(), result.getTotalAmount());
    }

    @Test
    void testUpdateReocrdInvoicable(){
        Long purchaseRecordId = 100L;
        Long organId = 200L;

        // when
        purchaseService.updateRecordInvoicable(purchaseRecordId, organId);

        // 校验状态更新
        verify(purchaseRecordMapper).update(argThat((LambdaUpdateWrapper wrapper) ->
                wrapper.getSqlSegment().contains("id")
                        && wrapper.getSqlSegment().contains("organ_id")
                        && wrapper.getSqlSet().contains("is_invocing")
        ));
    }

    @Test
    void testDelete(){
        purchaseService.deleteRecord(100L);

        // 校验状态更新
        verify(purchaseRecordMapper).update(argThat((LambdaUpdateWrapper wrapper) ->
                wrapper.getSqlSegment().contains("id")
                        && wrapper.getSqlSet().contains("deleted")
                        && wrapper.getSqlSet().contains("status")
        ));
    }

    @Test
    void testGetPurchaseRecord_emptyResult() {
        PurchaseRecordPageReqVO pageReqVO = new PurchaseRecordPageReqVO();


        when(purchaseRecordMapper.selectPage(pageReqVO)).thenReturn(new PageResult<>(null, 0L));
        PageResult<PurchaseRecordRespVO> result =
                purchaseService.getPurchaseRecord(pageReqVO);

        assertNotNull(result);
        assertEquals(0, result.getTotal());
        assertTrue(CollUtil.isEmpty(result.getList()));
    }

    @Test
    void testGetPurchaseRecord_invalidEndTime_realMapper() {
        // 准备参数
        PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setCreateTime(LocalDateTime.now());
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setProductName("测试产品");
            o.setTotalAmount(new BigDecimal("200.00"));
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(INVOICABLE.getCode());
            o.setInitial(false);
            o.setOrganId(1L);
        });

        PurchaseRecordPageReqVO pageReqVO = new PurchaseRecordPageReqVO();
        pageReqVO.setEndTime(new LocalDate[]{LocalDate.now()}); // length != 2

        when(purchaseRecordMapper.selectPage(pageReqVO)).thenReturn(new PageResult(List.of(recordDO), 1l));
        when(i18nUtils.getMessage(anyString())).thenReturn("支付宝");
        when(organService.getOrganWithDeleted(anyLong())).thenReturn(null);

        PageResult<PurchaseRecordRespVO> result =
                purchaseService.getPurchaseRecord(pageReqVO);

        assertEquals(1L, result.getTotal());
        PurchaseRecordRespVO respVO = result.getList().get(0);
        assertEquals("支付宝", respVO.getPaymentMethod());
        assertNull(respVO.getOrganName());
    }

    @Test
    void testGetPurchaseRecord_validEndTime_realMapper() {
        // 准备参数
        PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setCreateTime(LocalDateTime.now());
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setProductName("测试产品");
            o.setTotalAmount(new BigDecimal("200.00"));
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(INVOICABLE.getCode());
            o.setInitial(false);
            o.setOrganId(2L);
        });

        PurchaseRecordPageReqVO pageReqVO = new PurchaseRecordPageReqVO();
        pageReqVO.setEndTime(new LocalDate[]{
                LocalDate.now().minusDays(1),
                LocalDate.now()
        });
        when(purchaseRecordMapper.selectPage(pageReqVO)).thenReturn(new PageResult(List.of(recordDO), 1l));
        when(i18nUtils.getMessage(anyString())).thenReturn("支付宝");

        OrganizationDO organ = new OrganizationDO();
        organ.setId(2L);
        organ.setName("测试组织");
        when(organService.getOrganWithDeleted(2L)).thenReturn(organ);

        PageResult<PurchaseRecordRespVO> result =
                purchaseService.getPurchaseRecord(pageReqVO);

        assertEquals(1L, result.getTotal());
        PurchaseRecordRespVO respVO = result.getList().get(0);
        assertEquals("支付宝", respVO.getPaymentMethod());
        assertEquals("测试组织", respVO.getOrganName());
    }

    @Test
    void testGetOrgEarliestProductDetail_noPurchaseRecord() {
        OrgEarliestPurchaseProductRespVO resp =
                purchaseService.getOrgEarliestProductDetail(100L);

        assertNull(resp);
    }

    @Test
    void testGetOrgEarliestProductDetail_noDelayRecord() {
        Long organId = 101L;

        // 准备参数
        PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setCreateTime(LocalDateTime.now());
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setProductName("测试产品");
            o.setTotalAmount(new BigDecimal("200.00"));
            o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus());
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(INVOICABLE.getCode());
            o.setInitial(false);
            o.setOrganId(organId);
            o.setEndTime(LocalDate.now().plusDays(30));
        });
        when(purchaseRecordMapper.selectOne(any())).thenReturn(recordDO);

        // mock：无延期记录
        when(productDelayService.getRecordsByPurchaseId(recordDO.getId(), organId))
                .thenReturn(Collections.emptyList());

        OrgEarliestPurchaseProductRespVO resp =
                purchaseService.getOrgEarliestProductDetail(organId);

        assertNotNull(resp);
        PurchaseRecordDO purchaseRecordDO = resp.getPurchaseRecordDO();
        assertNotNull(purchaseRecordDO);
        // endTime 不应被覆盖
        assertEquals(recordDO.getEndTime(), purchaseRecordDO.getEndTime());
    }

    @Test
    void testGetOrgEarliestProductDetail_withDelayRecord() {
        Long organId = 102L;

        LocalDate originalEndTime = LocalDate.now().plusDays(30);
        LocalDate delayEndTime = LocalDate.now().plusDays(60);

        // 准备参数
        PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setCreateTime(LocalDateTime.now());
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setProductName("测试产品");
            o.setTotalAmount(new BigDecimal("200.00"));
            o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus());
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(INVOICABLE.getCode());
            o.setInitial(false);
            o.setOrganId(organId);
            o.setEndTime(originalEndTime);
        });
        when(purchaseRecordMapper.selectOne(any())).thenReturn(recordDO);

        // mock 延期记录（第一条为最新）
        ProductDelayRespVO delayVO = new ProductDelayRespVO();
        delayVO.setDelayTime(delayEndTime);

        when(productDelayService.getRecordsByPurchaseId(recordDO.getId(), organId))
                .thenReturn(Collections.singletonList(delayVO));

        OrgEarliestPurchaseProductRespVO resp =
                purchaseService.getOrgEarliestProductDetail(organId);

        assertNotNull(resp);
        PurchaseRecordDO purchaseRecordDO = resp.getPurchaseRecordDO();
        assertNotNull(purchaseRecordDO);
        // endTime 应被延期时间覆盖
        assertEquals(delayEndTime, purchaseRecordDO.getEndTime());
    }
    
    @Test
    void getInvoicingAmountTest(){
        BigDecimal amount = new BigDecimal(100);
        when(purchaseRecordMapper.getInvoicingAmount(anyLong())).thenReturn(amount);

        BigDecimal invoicingAmount = purchaseService.getInvoicingAmount(1l);
        assertEquals(invoicingAmount, amount);
    }

    @Test
    void testCreateOrgInitProductPurchaseRecord_single() {
        Long organId = 1L;
        Long productId = 10L;

        BigDecimal price = new BigDecimal("1000.00");

        // 请求参数
        OrganCreateProductPurchaseReqVO reqVO = new OrganCreateProductPurchaseReqVO();
        reqVO.setProductId(productId);
        reqVO.setPrice(price);

        // mock 产品存在
        ProductsDO productsDO = new ProductsDO();
        productsDO.setId(productId);
        productsDO.setProductName("测试产品");

        when(productsDetailService.validateProductExist(productId))
                .thenReturn(productsDO);

        // mock 雪花 ID 参数
        when(mybatisIdProperties.getWorkerId()).thenReturn(1L);
        when(mybatisIdProperties.getDatacenterId()).thenReturn(1L);

        // 执行
        purchaseService.createOrgInitProductPurchaseRecord(
                organId, Collections.singletonList(reqVO)
        );

        // 校验插入
        verify(purchaseRecordMapper).insert(argThat((PurchaseRecordDO purchaseRecordDO) ->
                purchaseRecordDO.getProductName().equals(productsDO.getProductName())
                        && purchaseRecordDO.getProductId().equals(productsDO.getId())
                        && purchaseRecordDO.getPurchaseDuration().equals(1)
                        && purchaseRecordDO.getPurchaseDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode())
                        && purchaseRecordDO.getProductDetailsId() == 0
                        && purchaseRecordDO.getStatus().equals(PurchaseRecordStatusEnum.ACTIVE.getStatus())
                        && purchaseRecordDO.getInitial().equals(true)
                        && purchaseRecordDO.getMonthAverageAmount().equals(new BigDecimal("100.00"))
                        && purchaseRecordDO.getTotalAmount().equals(price)
                        && purchaseRecordDO.getOriginalAmount().equals(price)
                        && purchaseRecordDO.getEndTime().equals(LocalDate.now().minusDays(1))
        ));
    }

    @Test
    void testCreateOrgInitProductPurchaseRecord_empty() {
        purchaseService.createOrgInitProductPurchaseRecord(
                null, Collections.emptyList()
        );

        // 查询数据库验证
        List<PurchaseRecordDO> records = purchaseRecordMapper.selectList();

        assertEquals(0, records.size());
    }

    @Test
    void testUpdateOrgInitProductPurchaseRecord_updateNotExist() {
        Long organId = 1L;

        // DB 中已有首购记录
        PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setCreateTime(LocalDateTime.now());
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setProductName("测试产品");
            o.setTotalAmount(new BigDecimal("200.00"));
            o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus());
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(INVOICABLE.getCode());
            o.setInitial(false);
            o.setOrganId(organId);
        });
        purchaseRecordMapper.insert(recordDO);

        // 入参：updateList 中的 purchaseId 不存在
        OrganCreateProductPurchaseReqVO reqVO = new OrganCreateProductPurchaseReqVO();
        reqVO.setPurchaseId(9999L);
        reqVO.setPrice(new BigDecimal("1000"));

        assertServiceException(() -> purchaseService.updateOrgInitProductPurchaseRecord(
                organId, Collections.singletonList(reqVO)
        ), ORG_PRODUCT_PURCHASE_NOT_EXIST_ERROR);
    }

    @Test
    void testUpdateOrgInitProductPurchaseRecord_duplicateCreate() {
        Long organId = 2L;
        Long productId = 10L;

        // DB 中已有首购记录
        PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setCreateTime(LocalDateTime.now());
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setProductName("测试产品");
            o.setTotalAmount(new BigDecimal("200.00"));
            o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus());
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(INVOICABLE.getCode());
            o.setInitial(true);
            o.setOrganId(organId);
            o.setProductId(productId);
        });
        when(purchaseRecordMapper.selectList(any())).thenReturn(List.of(recordDO));

        // 新增请求（purchaseId = null）
        OrganCreateProductPurchaseReqVO reqVO = new OrganCreateProductPurchaseReqVO();
        reqVO.setProductId(productId);
        reqVO.setPrice(new BigDecimal("500"));

        assertServiceException(() -> purchaseService.updateOrgInitProductPurchaseRecord(
                organId, Collections.singletonList(reqVO)
        ), ORG_PRODUCT_PURCHASE_EXIST_ERROR);
    }

    @Test
    void testUpdateOrgInitProductPurchaseRecord_success() {
        Long organId = 3L;

        // DB 中已有两条首购记录
        PurchaseRecordDO record1 = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(3001L);
            o.setOrganId(organId);
            o.setProductId(101L);
            o.setProductName("产品1");
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(INVOICABLE.getCode());
            o.setInitial(true);
            o.setDeleted(false);
        });

        PurchaseRecordDO record2 = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(3002L);
            o.setOrganId(organId);
            o.setProductId(102L);
            o.setProductName("产品2");
            o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode());
            o.setIsInvocing(INVOICABLE.getCode());
            o.setInitial(true);
            o.setDeleted(false);
        });

        when(purchaseRecordMapper.selectList(any())).thenReturn(List.of(record1, record2));

        // update 请求
        OrganCreateProductPurchaseReqVO updateReq = new OrganCreateProductPurchaseReqVO();
        updateReq.setPurchaseId(3001L);
        updateReq.setPrice(new BigDecimal("1000"));

        // create 请求
        OrganCreateProductPurchaseReqVO createReq = new OrganCreateProductPurchaseReqVO();
        createReq.setProductId(103L);
        createReq.setPrice(new BigDecimal("600"));

        // mock 产品校验
        ProductsDO productsDO = new ProductsDO();
        productsDO.setId(103L);
        productsDO.setProductName("产品3");

        when(productsDetailService.validateProductExist(103L))
                .thenReturn(productsDO);

        when(mybatisIdProperties.getWorkerId()).thenReturn(1L);
        when(mybatisIdProperties.getDatacenterId()).thenReturn(1L);

        // 执行
        purchaseService.updateOrgInitProductPurchaseRecord(
                organId, Arrays.asList(updateReq, createReq)
        );

        // 验证 update
        verify(purchaseRecordMapper).update(argThat((LambdaUpdateWrapper wrapper) ->
                wrapper.getSqlSegment().contains("id")
                        && wrapper.getSqlSegment().contains("organ_id")
                        && wrapper.getSqlSegment().contains("initial")
                        && wrapper.getSqlSet().contains("total_amount")
                        && wrapper.getSqlSet().contains("month_average_amount")
                        && wrapper.getSqlSet().contains("original_amount")
        ));

        // 验证 delete（3002 被删）
        verify(purchaseRecordMapper, times(1)).deleteByIds(List.of(record2.getId()));

        // 验证 create
        // 校验插入
        verify(purchaseRecordMapper).insert(argThat((PurchaseRecordDO purchaseRecordDO) ->
                purchaseRecordDO.getProductName().equals(productsDO.getProductName())
                        && purchaseRecordDO.getProductId().equals(productsDO.getId())
                        && purchaseRecordDO.getPurchaseDuration().equals(1)
                        && purchaseRecordDO.getPurchaseDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode())
                        && purchaseRecordDO.getProductDetailsId() == 0
                        && purchaseRecordDO.getStatus().equals(PurchaseRecordStatusEnum.ACTIVE.getStatus())
                        && purchaseRecordDO.getInitial().equals(true)
                        && purchaseRecordDO.getMonthAverageAmount().equals(new BigDecimal("60.00"))
                        && purchaseRecordDO.getTotalAmount().equals(createReq.getPrice())
                        && purchaseRecordDO.getOriginalAmount().equals(createReq.getPrice())
                        && purchaseRecordDO.getEndTime().equals(LocalDate.now().minusDays(1))
        ));
    }

    @Test
    void testGetOrgPurchaseList() {
        Long organId1 = 1L;

        LocalDateTime now = LocalDateTime.now();

        // organId1
        PurchaseRecordDO r1 = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1001L);
            o.setOrganId(organId1);
            o.setDeleted(false);
            o.setCreateTime(now);
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(INVOICABLE.getCode());
        });
        when(purchaseRecordMapper.selectList(any())).thenReturn(List.of(r1));

        Set<Long> organIds = new HashSet<>(Arrays.asList(organId1));

        List<PurchaseRecordDO> list = purchaseService.getOrgPurchaseList(organIds);

        // 只剩 1 条
        assertEquals(1, list.size());
    }

    @Test
    void testGetTotalSales() {
        // 准备数据
        TotalSalesAmountRespVO totalSalesAmountRespVO = new TotalSalesAmountRespVO(new BigDecimal("400.00"));
        when(purchaseRecordMapper.getTotalSales()).thenReturn(totalSalesAmountRespVO);

        TotalSalesAmountRespVO resp = purchaseService.getTotalSales();

        assertNotNull(resp);
        assertEquals(new BigDecimal("400.00"), resp.getAmount());
    }

    @Test
    void testGetManageSalesDetailsPage_success() {
        SalesDetailsPageReqVO reqVO = new SalesDetailsPageReqVO();

        Long organId = 1L;

        PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1001L);
            o.setOrganId(organId);
            o.setExternalSpent(new BigDecimal("200.00"));
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setCreateTime(LocalDateTime.now());
            o.setIsInvocing(INVOICINGSUCCESSFUL.getCode());
            o.setInitial(false);
        });

        when(purchaseRecordMapper.getSaleDetailsPage(reqVO)).thenReturn(new PageResult<>(List.of(recordDO), 1L));

        // mock 国际化
        when(i18nUtils.getMessage(anyString())).thenReturn("支付宝");

        // mock 组织
        OrganizationDO organ = new OrganizationDO();
        organ.setId(organId);
        organ.setName("测试组织");

        when(organService.getOrganWithDeleted(organId)).thenReturn(organ);

        PageResult<SalesDetailRespVO> pageResult =
                purchaseService.getManageSalesDetailsPage(reqVO);

        assertEquals(1L, pageResult.getTotal());
        assertEquals(1, pageResult.getList().size());

        SalesDetailRespVO respVO = pageResult.getList().get(0);

        assertEquals(TradeTypeEnum.PRODUCT.getCode(), respVO.getTradeType());
        assertEquals(new BigDecimal("200.00"), respVO.getRechargeSpent());
        assertEquals("支付宝", respVO.getPaymentMethod());
        assertEquals("测试组织", respVO.getOrganName());
    }

    @Test
    void testGetManageSalesDetailsPage_organNotExist() {
        SalesDetailsPageReqVO reqVO = new SalesDetailsPageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(10);

        Long organId = 2L;

        PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1002L);
            o.setOrganId(organId);
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode());
            o.setCreateTime(LocalDateTime.now());
            o.setIsInvocing(INVOICINGSUCCESSFUL.getCode());
            o.setInitial(false);
        });

        when(purchaseRecordMapper.getSaleDetailsPage(reqVO)).thenReturn(new PageResult<>(List.of(recordDO), 1L));

        when(i18nUtils.getMessage(anyString())).thenReturn("微信");
        when(organService.getOrganWithDeleted(organId)).thenReturn(null);

        PageResult<SalesDetailRespVO> pageResult =
                purchaseService.getManageSalesDetailsPage(reqVO);

        SalesDetailRespVO respVO = pageResult.getList().get(0);

        assertEquals("微信", respVO.getPaymentMethod());
        assertNull(respVO.getOrganName());
    }

    @Test
    void testGetOrgInitialPurchaseRecord_success() {
        Long organId = 1L;
        Long productId = 100L;

        PurchaseRecordDO initialRecord = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1001L);
            o.setOrganId(organId);
            o.setProductId(productId);
            o.setInitial(true);
            o.setDeleted(false);
            o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode());
            o.setIsInvocing(INVOICINGSUCCESSFUL.getCode());
        });

        when(purchaseRecordMapper.selectOne(any())).thenReturn(initialRecord);

        PurchaseRecordDO result =
                purchaseService.getOrgInitialPurchaseRecord(organId, productId);

        assertNotNull(result);
        assertEquals(initialRecord.getId(), result.getId());
        assertTrue(result.getInitial());
    }

    @Test
    void testGetOrgProductPendingPurchaseList_success() {
        Long organId = 3L;
        Long productId = 300L;

        PurchaseRecordDO pending1 = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(2001L);
            o.setOrganId(organId);
            o.setProductId(productId);
            o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus());
            o.setDeleted(false);
            o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode());
            o.setIsInvocing(INVOICINGSUCCESSFUL.getCode());
        });

        when(purchaseRecordMapper.selectList(any())).thenReturn(List.of(pending1));

        List<PurchaseRecordDO> result =
                purchaseService.getOrgProductPendingPurchaseList(organId, productId);

        assertEquals(1, result.size());
        assertTrue(result.stream()
                .allMatch(r -> PurchaseRecordStatusEnum.PENDING.getStatus().equals(r.getStatus())));
    }

    @Test
    void test_noInitialPurchase_withYearPricing_noPromotion() {
        Long productId = 100L;
        Long organId = 1L;

        ProductsDO product = new ProductsDO();
        product.setId(productId);
        product.setPrice(new BigDecimal("1200"));

        when(productsDetailService.validateProductExist(productId))
                .thenReturn(product);

        ProductsDetailDO yearDetail = new ProductsDetailDO();
        yearDetail.setDuration(1);
        yearDetail.setDurationUnit(ProductDurationUnitEnum.YEAR.getCode());
        yearDetail.setDiscount(new BigDecimal("80"));

        when(productsDetailService.getPage(any()))
                .thenReturn(new PageResult<>(List.of(yearDetail), 1L));

        when(productPromotionService.getAvailableProductPromotionList(productId))
                .thenReturn(Collections.emptyList());

        List<ProductDetailsRespVO> result =
                purchaseService.getOrgProductDetailList(organId, productId);

        // 10个月 + 1年
        assertEquals(11, result.size());

        ProductDetailsRespVO year =
                result.stream()
                        .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode()))
                        .findFirst().orElseThrow();

        assertEquals(new BigDecimal("1200"), year.getOriginalPrice());
        assertEquals(new BigDecimal("960.00"), year.getCurrentPrice());
        assertNull(year.getGiftDuration());
    }

    @Test
    void test_withInitialPurchase_withYearPricing_withPromotion() {
        Long productId = 100L;
        Long organId = 1L;

        ProductsDO product = new ProductsDO();
        product.setId(productId);
        product.setPrice(new BigDecimal("999"));

        when(productsDetailService.validateProductExist(productId))
                .thenReturn(product);

        ProductsDetailDO yearDetail = new ProductsDetailDO();
        yearDetail.setDuration(2);
        yearDetail.setDurationUnit(ProductDurationUnitEnum.YEAR.getCode());
        yearDetail.setDiscount(new BigDecimal("50"));

        when(productsDetailService.getPage(any()))
                .thenReturn(new PageResult<>(List.of(yearDetail), 1L));

        ProductPromotionDO promotion = new ProductPromotionDO();
        promotion.setPurchaseDuration(2);
        promotion.setPurchaseDurationUnit(ProductDurationUnitEnum.YEAR.getCode());
        promotion.setGiftDuration(3);
        promotion.setGiftDurationUnit(ProductDurationUnitEnum.MONTH.getCode());

        when(productPromotionService.getAvailableProductPromotionList(productId))
                .thenReturn(List.of(promotion));

        PurchaseRecordDO recordDO = new PurchaseRecordDO();
        recordDO.setTotalAmount(new BigDecimal("1000"));
        recordDO.setMonthAverageAmount(new BigDecimal("100"));

        ProductDetailsRespVO year =
                purchaseService.getOrgProductDetailList(organId, productId)
                        .stream()
                        .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode()))
                        .findFirst().orElseThrow();

        // 原价：1000 * 2
        assertEquals(new BigDecimal("1998"), year.getOriginalPrice());
        assertEquals(new BigDecimal("999.00"), year.getCurrentPrice());

        // promotion 命中
        assertEquals(3, year.getGiftDuration());
        assertEquals(ProductDurationUnitEnum.MONTH.getCode(), year.getGiftDurationUnit());
    }

    @Test
    void test_withInitialPurchase_withoutYearPricing_autoFillYear() {
        Long productId = 100L;
        Long organId = 1L;

        ProductsDO product = new ProductsDO();
        product.setId(productId);
        product.setPrice(new BigDecimal("888"));

        when(productsDetailService.validateProductExist(productId))
                .thenReturn(product);

        // 只有 MONTH
        ProductsDetailDO monthDetail = new ProductsDetailDO();
        monthDetail.setDuration(6);
        monthDetail.setDurationUnit(ProductDurationUnitEnum.MONTH.getCode());

        when(productsDetailService.getPage(any()))
                .thenReturn(new PageResult<>(List.of(monthDetail), 1L));

        when(productPromotionService.getAvailableProductPromotionList(productId))
                .thenReturn(Collections.emptyList());

        PurchaseRecordDO recordDO = new PurchaseRecordDO();
        recordDO.setTotalAmount(new BigDecimal("888"));
        recordDO.setMonthAverageAmount(new BigDecimal("88.8"));

        List<ProductDetailsRespVO> result =
                purchaseService.getOrgProductDetailList(organId, productId);

        ProductDetailsRespVO year =
                result.stream()
                        .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode()))
                        .findFirst().orElseThrow();

        assertEquals(new BigDecimal("888"), year.getCurrentPrice());
    }


    @Test
    void test_getInitialProductProductPriceNullError(){
        Long productId = 100L;
        Long organId = 1L;

        ProductsDO product = new ProductsDO();
        product.setId(productId);

        when(productsDetailService.validateProductExist(productId))
                .thenReturn(product);

        when(productsDetailService.getPage(any()))
                .thenReturn(new PageResult<>(anyList(), 1L));

        ServiceException ex = assertThrows(ServiceException.class, () ->
                purchaseService.getOrgProductDetailList(organId, productId)
        );

        assertEquals(PRODUCT_PRICE_NULL_ERROR.getCode(), ex.getCode());
    }

    @Test
    void test_getOrgProductDetailList_WithOrgInitial() {
        Long productId = 100L;
        Long organId = 1L;

        ProductsDO product = new ProductsDO();
        product.setId(productId);
        product.setPrice(new BigDecimal("1000"));

        when(productsDetailService.validateProductExist(productId))
                .thenReturn(product);

        ProductsDetailDO yearDetail = new ProductsDetailDO();
        yearDetail.setDuration(2);
        yearDetail.setDurationUnit(ProductDurationUnitEnum.YEAR.getCode());
        yearDetail.setDiscount(new BigDecimal("60"));
        when(productsDetailService.getPage(any()))
                .thenReturn(new PageResult<>(List.of(yearDetail), 1L));

        ProductPromotionDO productPromotionDO = new ProductPromotionDO();
        productPromotionDO.setPurchaseDuration(2);
        productPromotionDO.setPurchaseDurationUnit(ProductDurationUnitEnum.YEAR.getCode());
        // 优惠活动
        when(productPromotionService.getAvailableProductPromotionList(productId))
                .thenReturn(Collections.singletonList(productPromotionDO));

        // 插入首购记录
        PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setOrganId(organId);
            o.setProductId(productId);
            o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus());
            o.setDeleted(false);
            o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode());
            o.setIsInvocing(INVOICINGSUCCESSFUL.getCode());
            o.setInitial(true);
            o.setMonthAverageAmount(new BigDecimal("99"));
            o.setTotalAmount(new BigDecimal("999"));
        });
        when(purchaseRecordMapper.selectOne(any())).thenReturn(recordDO);

        List<ProductDetailsRespVO> result =
                purchaseService.getOrgProductDetailList(organId, productId);

        ProductDetailsRespVO year =
                result.stream()
                        .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode()))
                        .findFirst().orElseThrow();

        assertEquals(new BigDecimal("1198.80"), year.getCurrentPrice());
        assertEquals(new BigDecimal("1998"), year.getOriginalPrice());
    }

    @Test
    void test_getOrgProductDetailList_WithoutYear() {
        Long productId = 100L;
        Long organId = 1L;

        ProductsDO product = new ProductsDO();
        product.setId(productId);
        product.setPrice(new BigDecimal("1000"));

        when(productsDetailService.validateProductExist(productId))
                .thenReturn(product);

        ProductsDetailDO monthDetail = new ProductsDetailDO();
        monthDetail.setDuration(2);
        monthDetail.setDurationUnit(ProductDurationUnitEnum.MONTH.getCode());
        when(productsDetailService.getPage(any()))
                .thenReturn(new PageResult<>(List.of(monthDetail), 1L));

        ProductPromotionDO productPromotionDO = new ProductPromotionDO();
        productPromotionDO.setPurchaseDuration(2);
        productPromotionDO.setPurchaseDurationUnit(ProductDurationUnitEnum.YEAR.getCode());
        // 优惠活动
        when(productPromotionService.getAvailableProductPromotionList(productId))
                .thenReturn(Collections.singletonList(productPromotionDO));

        // 插入首购记录
        PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setOrganId(organId);
            o.setProductId(productId);
            o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus());
            o.setDeleted(false);
            o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode());
            o.setIsInvocing(INVOICINGSUCCESSFUL.getCode());
            o.setInitial(true);
            o.setMonthAverageAmount(new BigDecimal("99"));
            o.setTotalAmount(new BigDecimal("999"));
        });
        when(purchaseRecordMapper.selectOne(any())).thenReturn(recordDO);

        List<ProductDetailsRespVO> result =
                purchaseService.getOrgProductDetailList(organId, productId);

        ProductDetailsRespVO year =
                result.stream()
                        .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode()))
                        .findFirst().orElseThrow();

        assertEquals(1, year.getDuration());
        assertEquals(new BigDecimal("1000"), year.getCurrentPrice());
        assertEquals(new BigDecimal("1000"), year.getOriginalPrice());
    }

    @Test
    void should_not_update_when_status_is_not_active_or_pending() {
        Long organId = 1L;
        Long recordId = 101L;

        purchaseService.invalidHistoryOrganProductPurchaseRecord(organId, recordId);

        // 校验状态更新
        verify(purchaseRecordMapper).update(argThat((LambdaUpdateWrapper wrapper) ->
                wrapper.getSqlSegment().contains("organ_id")
                        && wrapper.getSqlSegment().contains("id")
                        && wrapper.getSqlSegment().contains("deleted")
                        && wrapper.getSqlSegment().contains("status")
                        && wrapper.getSqlSet().contains("status")
        ));
    }

    @Test
    void should_activate_record_when_status_is_pending_and_not_deleted() {
        Long organId = 1L;
        Long recordId = 200L;

        // when
        purchaseService.activateHistoryOrganProductPurchaseRecord(organId, recordId);

        // 校验状态更新
        verify(purchaseRecordMapper).update(argThat((LambdaUpdateWrapper wrapper) ->
                wrapper.getSqlSegment().contains("organ_id")
                        && wrapper.getSqlSegment().contains("id")
                        && wrapper.getSqlSegment().contains("deleted")
                        && wrapper.getSqlSegment().contains("status")
                        && wrapper.getSqlSet().contains("status")
        ));
    }

    @Test
    void should_return_correct_sales_statistics() {

        TotalSalesAmountRespVO totalSalesAmountRespVO = new TotalSalesAmountRespVO(new BigDecimal(300));
        when(purchaseRecordMapper.getTotalSales()).thenReturn(totalSalesAmountRespVO);
        when(purchaseRecordMapper.getTodaySales(any())).thenReturn(new BigDecimal(100));

        // when
        ManageSalesTotalStatisticRespVO resp =
                purchaseService.getSalesTotalStatistics();

        // then
        assertAll(
                () -> assertEquals(new BigDecimal("300"), resp.getSalesTotal()),
                () -> assertEquals(new BigDecimal("100"), resp.getSalesToday())
        );
    }
}