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

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.test.core.ut.BaseMockitoUnitTest;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductDelayRespVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelaySaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.delay.ProductDelayRecordDO;
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.delay.ProductDelayRecordMapper;
import com.cf.imes.module.system.dal.mysql.funds.purchase.PurchaseRecordMapper;
import com.cf.imes.module.system.enums.pay.DurationUnitEnum;
import com.cf.imes.module.system.service.funds.products.ProductsDetailService;
import com.cf.imes.module.system.service.funds.purchase.PurchaseService;
import com.cf.imes.module.system.service.organ.OrganService;
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.time.LocalDate;
import java.util.Collections;
import java.util.List;

import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceException;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_DELAY_UNIT_NOT_SUPPORT;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_PURCHASE_NO_EXIST;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

class ProductDelayServiceImplTest extends BaseMockitoUnitTest {

    @InjectMocks
    private ProductDelayServiceImpl productDelayService;

    @Mock
    private ProductDelayRecordMapper productDelayRecordMapper;

    @Mock
    private PurchaseRecordMapper purchaseRecordMapper;

    @Mock
    private OrganService organService;

    @Mock
    private PurchaseService purchaseService;

    @Mock
    private ProductsDetailService productsDetailService;

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

    @Test
    void testCreateProductDelay_purchaseNotExist() {
        ProductDelaySaveReqVO reqVO = new ProductDelaySaveReqVO();
        reqVO.setOrganId(1L);
        reqVO.setPurchaseId(10L);

        when(organService.validOrgan(1L)).thenReturn(new OrganizationDO());
        when(purchaseService.getRecord(10L)).thenReturn(null);

        assertServiceException(() -> productDelayService.createProductDelay(reqVO), PRODUCT_PURCHASE_NO_EXIST);
    }

    @Test
    void testCreateProductDelay_success_noLastDelay_notExpired() {
        LocalDate now = LocalDate.now();

        ProductDelaySaveReqVO reqVO = new ProductDelaySaveReqVO();
        reqVO.setOrganId(1L);
        reqVO.setPurchaseId(100L);
        reqVO.setDelayDuration(10);
        reqVO.setDelayDurationUnit(DurationUnitEnum.DAY.getCode());

        OrganizationDO org = new OrganizationDO();
        org.setName("组织A");

        PurchaseRecordDO purchase = new PurchaseRecordDO();
        purchase.setProductId(11L);
        purchase.setProductName("产品A");
        purchase.setEndTime(now.plusDays(5));

        when(organService.validOrgan(1L)).thenReturn(org);
        when(purchaseService.getRecord(100L)).thenReturn(purchase);
        when(productDelayRecordMapper.selectOne(any())).thenReturn(null);

        productDelayService.createProductDelay(reqVO);

        verify(productDelayRecordMapper).insert(argThat((ProductDelayRecordDO recordDO) ->
                recordDO.getEndTime().equals(purchase.getEndTime())
                        && recordDO.getDelayTime().equals(purchase.getEndTime().plusDays(10))
                        && !recordDO.getPaid()
        ));
    }

    @Test
    void testCreateProductDelay_success_purchaseExpired() {
        LocalDate now = LocalDate.now();

        ProductDelaySaveReqVO reqVO = new ProductDelaySaveReqVO();
        reqVO.setOrganId(1L);
        reqVO.setPurchaseId(200L);
        reqVO.setDelayDuration(1);
        reqVO.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());

        when(organService.validOrgan(any())).thenReturn(new OrganizationDO());
        when(purchaseService.getRecord(any())).thenReturn(
                PurchaseRecordDO.builder()
                        .productId(1L)
                        .productName("产品")
                        .endTime(now.minusDays(3))
                        .build()
        );
        when(productDelayRecordMapper.selectOne(any())).thenReturn(null);

        productDelayService.createProductDelay(reqVO);

        verify(productDelayRecordMapper).insert(argThat((ProductDelayRecordDO r) ->
                r.getEndTime().equals(now)
                        && r.getDelayTime().equals(now.plusMonths(1))
        ));
    }


    @Test
    void testCreateProductDelay_success_withLastDelay() {
        LocalDate base = LocalDate.now().plusDays(5);

        when(productDelayRecordMapper.selectOne(any()))
                .thenReturn(ProductDelayRecordDO.builder()
                        .delayTime(base)
                        .build());

        when(organService.validOrgan(any())).thenReturn(new OrganizationDO());
        when(purchaseService.getRecord(any())).thenReturn(
                PurchaseRecordDO.builder()
                        .productId(1L)
                        .productName("产品")
                        .endTime(LocalDate.now())
                        .build()
        );

        ProductDelaySaveReqVO reqVO = new ProductDelaySaveReqVO();
        reqVO.setOrganId(1L);
        reqVO.setPurchaseId(1L);
        reqVO.setDelayDuration(2);
        reqVO.setDelayDurationUnit(DurationUnitEnum.YEAR.getCode());

        productDelayService.createProductDelay(reqVO);

        verify(productDelayRecordMapper).insert(argThat((ProductDelayRecordDO r) ->
                r.getEndTime().equals(base)
                        && r.getDelayTime().equals(base.plusYears(2))
        ));
    }

    @Test
    void testCreateProductDelay_unitNotSupport() {
        ProductDelaySaveReqVO reqVO = new ProductDelaySaveReqVO();
        reqVO.setOrganId(1L);
        reqVO.setPurchaseId(1L);
        reqVO.setDelayDuration(1);
        reqVO.setDelayDurationUnit(999); // 非法

        when(organService.validOrgan(any())).thenReturn(new OrganizationDO());
        when(purchaseService.getRecord(any())).thenReturn(
                PurchaseRecordDO.builder()
                        .productId(1L)
                        .productName("产品")
                        .endTime(LocalDate.now())
                        .build()
        );
        when(productDelayRecordMapper.selectOne(any())).thenReturn(null);

        assertServiceException(() -> productDelayService.createProductDelay(reqVO), PRODUCT_DELAY_UNIT_NOT_SUPPORT);
    }

    @Test
    void testGetRecordsByPurchaseId_success() {
        Long purchaseId = 100L;
        Long organId = 200L;

        ProductDelayRecordDO recordDO = new ProductDelayRecordDO();
        recordDO.setDelayDuration(6);
        recordDO.setDelayDurationUnit(2);
        recordDO.setDelayTime(LocalDate.now());

        when(productDelayRecordMapper.selectList(any()))
                .thenReturn(List.of(recordDO));

        List<ProductDelayRespVO> result =
                productDelayService.getRecordsByPurchaseId(purchaseId, organId);

        assertEquals(1, result.size());
        assertEquals(6, result.get(0).getDelayDuration());
        assertEquals(2, result.get(0).getDelayDurationUnit());

        verify(productDelayRecordMapper).selectList(any());
    }

    @Test
    void testGetRecordsByPurchaseId_empty() {
        when(productDelayRecordMapper.selectList(any()))
                .thenReturn(Collections.emptyList());

        List<ProductDelayRespVO> result =
                productDelayService.getRecordsByPurchaseId(1L, 1L);

        assertNotNull(result);
        assertTrue(result.isEmpty());
    }

    @Test
    void testGetPage_success() {
        ProductDelayPageReqVO reqVO = new ProductDelayPageReqVO();

        ProductDelayRecordDO recordDO = new ProductDelayRecordDO();
        recordDO.setId(1L);
        recordDO.setDelayDuration(3);
        recordDO.setDelayDurationUnit(1);

        PageResult<ProductDelayRecordDO> pageResult =
                new PageResult<>(List.of(recordDO), 1L);

        when(productDelayRecordMapper.selectPage(reqVO))
                .thenReturn(pageResult);

        PageResult<ProductDelayRespVO> result =
                productDelayService.getPage(reqVO);

        assertEquals(1L, result.getTotal());
        assertEquals(1, result.getList().size());
        assertEquals(3, result.getList().get(0).getDelayDuration());

        verify(productDelayRecordMapper).selectPage(reqVO);
    }

    @Test
    void testGetActiveRecordsByProductId_noActivePurchase() {
        Long productId = 1L;
        Long organId = 2L;

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

        PreviouProductDelayRespVO result =
                productDelayService.getActiveRecordsByProductId(productId, organId);

        assertNotNull(result);
        assertNull(result.getPreviouPurchaseRecord());
        assertTrue(
                result.getDelayRespVOList() == null
                        || result.getDelayRespVOList().isEmpty()
        );

        verify(purchaseRecordMapper).selectOne(any());
        verifyNoInteractions(productDelayRecordMapper);
    }

    @Test
    void testGetActiveRecordsByProductId_success() {
        Long productId = 1L;
        Long organId = 2L;

        PurchaseRecordDO purchase = new PurchaseRecordDO();
        purchase.setId(10L);
        purchase.setProductId(productId);
        purchase.setOrganId(organId);

        ProductDelayRecordDO delay = new ProductDelayRecordDO();
        delay.setDelayDuration(3);
        delay.setDelayDurationUnit(1);
        delay.setDelayTime(LocalDate.now());

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

        when(productDelayRecordMapper.selectList(any()))
                .thenReturn(List.of(delay));

        PreviouProductDelayRespVO result =
                productDelayService.getActiveRecordsByProductId(productId, organId);

        assertNotNull(result);

        // 断言 purchase
        assertNotNull(result.getPreviouPurchaseRecord());
        assertEquals(10L, result.getPreviouPurchaseRecord().getId());

        // 断言 delay 列表
        assertNotNull(result.getDelayRespVOList());
        assertEquals(1, result.getDelayRespVOList().size());
        assertEquals(3, result.getDelayRespVOList().get(0).getDelayDuration());

        verify(purchaseRecordMapper).selectOne(any());
        verify(productDelayRecordMapper).selectList(any());
    }

    @Test
    void updatePurchaseDelayRecordsPaidAndRemark_success() {
        // given
        Long organId = 1L;
        Boolean paid = true;

        ProductDelayRecordDO recordDO = new ProductDelayRecordDO();
        recordDO.setId(100L);
        recordDO.setRemark("test remark");

        List<ProductDelayRecordDO> list = Collections.singletonList(recordDO);

        // mock update 行为
        when(productDelayRecordMapper.update(any(LambdaUpdateWrapper.class)))
                .thenReturn(1);

        // when
        productDelayService.updatePurchaseDelayRecordsPaidAndRemark(list, organId, paid);

        // then
        verify(productDelayRecordMapper, times(1))
                .update(any(LambdaUpdateWrapper.class));
    }
    @Test
    void updatePurchaseDelayRecordsPaid_success() {
        // given
        Long purchaseId = 10L;
        Long organId = 1L;

        when(productDelayRecordMapper.update(any(LambdaUpdateWrapper.class)))
                .thenReturn(2); // 模拟更新 2 条

        // when
        productDelayService.updatePurchaseDelayRecordsPaid(purchaseId, organId);

        // then
        verify(productDelayRecordMapper, times(1))
                .update(any(LambdaUpdateWrapper.class));
    }


    @Test
    void resetPurchaseDelayRecordsRemark_success() {
        // given
        Long purchaseId = 10L;
        Long organId = 1L;

        when(productDelayRecordMapper.update(any(LambdaUpdateWrapper.class)))
                .thenReturn(1);

        // when
        productDelayService.resetPurchaseDelayRecordsRemark(purchaseId, organId);

        // then
        verify(productDelayRecordMapper, times(1))
                .update(any(LambdaUpdateWrapper.class));
    }

}
