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

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.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest;
import com.cf.imes.module.infra.api.file.FileApi;
import com.cf.imes.module.system.controller.admin.funds.advertisement.vo.AdvertisementPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.advertisement.vo.AdvertisementRePublishReqVO;
import com.cf.imes.module.system.controller.admin.funds.advertisement.vo.AdvertisementRespVO;
import com.cf.imes.module.system.controller.admin.funds.advertisement.vo.AdvertisementSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.advertisement.AdvertisementDO;
import com.cf.imes.module.system.dal.mysql.funds.advertisement.AdvertisementMapper;
import com.cf.imes.module.system.enums.advertisement.AdvertisementPositionEnum;
import com.cf.imes.module.system.enums.advertisement.AdvertisementStatusEnum;
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 org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
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.ADVERTISEMENT_IMAGE_EMPTY_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ADVERTISEMENT_IMAGE_UPLOAD_FAIL;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ADVERTISEMENT_NAME_IS_EXIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ADVERTISEMENT_NO_DELETE;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ADVERTISEMENT_NO_EXIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ADVERTISEMENT_STATUS_ENDTIME_EXPIRED;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ADVERTISEMENT_TIME_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ADVERTISEMENT_UPDATE_STATUS_ERROR;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * @author Gqr
 * @since 2025/10/28 10:15
 */
class AdvertisementServiceImplTest extends BaseMockitoUnitTest {
    @InjectMocks
    private AdvertisementServiceImpl advertisementService;

    @Mock
    private AdvertisementMapper advertisementMapper;

    @Mock
    private FileApi fileApi;

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

    @Test
    void testCreate_whenStartTimeAfterEndTime_shouldThrow() {
        AdvertisementSaveReqVO reqVO = new AdvertisementSaveReqVO();
        reqVO.setStartTime(LocalDate.now().plusDays(1));
        reqVO.setEndTime(LocalDate.now());

        assertServiceException(() -> advertisementService.create(reqVO), ADVERTISEMENT_TIME_ERROR);
    }

    @Test
    void testCreate_whenAdNameExists() {
        // 1. 准备入参（时间必须合法）
        AdvertisementSaveReqVO reqVO = new AdvertisementSaveReqVO();
        reqVO.setAdName("TestAd");
        reqVO.setStartTime(LocalDate.now());
        reqVO.setEndTime(LocalDate.now().plusDays(1));

        // 2. mock：广告名已存在
        AdvertisementDO exist = new AdvertisementDO();
        exist.setId(1L);
        exist.setAdName("TestAd");

        when(advertisementMapper.selectAdByAdName(
                eq("TestAd"),
                isNull()
        )).thenReturn(exist);

        assertServiceException(() -> advertisementService.create(reqVO), ADVERTISEMENT_NAME_IS_EXIST);

        // 4. insert 一定不能被调用
        verify(advertisementMapper, never()).insert(any(AdvertisementDO.class));
    }

    @Test
    void testCreate_success() {
        AdvertisementSaveReqVO reqVO = new AdvertisementSaveReqVO();
        reqVO.setStartTime(LocalDate.now());
        reqVO.setEndTime(LocalDate.now().plusDays(1));
        reqVO.setAdName("Ad-CREATE-1");
        reqVO.setAdPosition(List.of(AdvertisementPositionEnum.SYSTEMCONFIG_AMOUNTQUERY_CAROUSEL.getStatus()
                , AdvertisementPositionEnum.WORKBENCH_CAROUSEL.getStatus()
                , AdvertisementPositionEnum.BASECONFIG_FUNDSQUERY_CAROUSEL.getStatus()));
        reqVO.setAdImagePath("https://example.com/image.png");

        when(advertisementMapper.selectAdByAdName(eq("Ad-CREATE-1"), isNull()))
                .thenReturn(null);

        // 3. mock insert：模拟 MyBatis 回填 id
        doAnswer(invocation -> {
            AdvertisementDO ad = invocation.getArgument(0);
            ad.setId(100L);
            return 1;
        }).when(advertisementMapper).insert(any(AdvertisementDO.class));

        // 4. 调用方法
        Long id = advertisementService.create(reqVO);

        // 5. 断言返回值
        assertEquals(100L, id);

        // 6. 捕获 insert 的参数，断言字段
        ArgumentCaptor<AdvertisementDO> captor =
                ArgumentCaptor.forClass(AdvertisementDO.class);
        verify(advertisementMapper).insert(captor.capture());

        AdvertisementDO saved = captor.getValue();
        assertEquals("Ad-CREATE-1", saved.getAdName());
        assertEquals(AdvertisementStatusEnum.UNPUBLISHED.getStatus(), saved.getStatus());
        assertEquals(List.of(AdvertisementPositionEnum.WORKBENCH_CAROUSEL.getStatus()
                , AdvertisementPositionEnum.SYSTEMCONFIG_AMOUNTQUERY_CAROUSEL.getStatus()
                , AdvertisementPositionEnum.BASECONFIG_FUNDSQUERY_CAROUSEL.getStatus()), saved.getAdPosition());
    }

    @Test
    void testUpdate_whenStartTimeAfterEndTime() {
        AdvertisementSaveReqVO reqVO = new AdvertisementSaveReqVO();
        reqVO.setStartTime(LocalDate.now().plusDays(1));
        reqVO.setEndTime(LocalDate.now());

        assertServiceException(() -> advertisementService.update(reqVO), ADVERTISEMENT_TIME_ERROR);
    }

    @Test
    void testUpdate_adExist(){
        AdvertisementSaveReqVO reqVO = new AdvertisementSaveReqVO();
        reqVO.setId(123L);
        reqVO.setStartTime(LocalDate.now());
        reqVO.setEndTime(LocalDate.now().plusDays(1));
        assertServiceException(() -> advertisementService.update(reqVO), ADVERTISEMENT_NO_EXIST);
    }

    @Test
    void testUpdate_whenAdNameExists() {
        AdvertisementSaveReqVO reqVO = new AdvertisementSaveReqVO();
        reqVO.setStartTime(LocalDate.now());
        reqVO.setEndTime(LocalDate.now().plusDays(1));
        reqVO.setAdName("Ad-UPDATE-1");

        AdvertisementDO exist = new AdvertisementDO();
        when(advertisementMapper.selectOne(any()))
                .thenReturn(exist);

        when(advertisementMapper.selectAdByAdName(
                eq("Ad-UPDATE-1"),
                isNull()
        )).thenReturn(exist);

        assertServiceException(() -> advertisementService.update(reqVO), ADVERTISEMENT_NAME_IS_EXIST);

        // 4. insert 一定不能被调用
        verify(advertisementMapper, never()).updateById(any(AdvertisementDO.class));
    }

    @Test
    void testUpdate_success() {
        // ========== 1. mock：广告已存在 ==========
        AdvertisementDO existing = new AdvertisementDO();
        existing.setId(1L);
        existing.setDeleted(false);

        // validateAdvertisementExists() 内部会调用这个
        when(advertisementMapper.selectOne(any()))
                .thenReturn(existing);

        // ========== 2. 构造更新请求 ==========
        AdvertisementSaveReqVO reqVO = new AdvertisementSaveReqVO();
        reqVO.setId(1L);
        reqVO.setAdName("UpdatedAd");
        reqVO.setStartTime(LocalDate.now());
        reqVO.setEndTime(LocalDate.now().plusDays(1));
        reqVO.setAdPosition(List.of(AdvertisementPositionEnum.SYSTEMCONFIG_AMOUNTQUERY_CAROUSEL.getStatus()
                , AdvertisementPositionEnum.WORKBENCH_CAROUSEL.getStatus()
                , AdvertisementPositionEnum.BASECONFIG_FUNDSQUERY_CAROUSEL.getStatus()));

        // ========== 3. 执行 ==========
        advertisementService.update(reqVO);

        // ========== 4. 验证 updateById 被调用 ==========
        ArgumentCaptor<AdvertisementDO> captor =
                ArgumentCaptor.forClass(AdvertisementDO.class);

        verify(advertisementMapper).updateById(captor.capture());

        AdvertisementDO updated = captor.getValue();

        // ========== 5. 断言关键字段 ==========
        assertEquals(1L, updated.getId());
        assertEquals("UpdatedAd", updated.getAdName());
    }

    @Test
    void testDelete_notExist() {
        // mock 不存在
        when(advertisementMapper.selectOne(any()))
                .thenReturn(null);

        assertServiceException(() -> advertisementService.delete(123L), ADVERTISEMENT_NO_EXIST);

        // 不应该做任何删除
        verify(advertisementMapper, never()).deleteById(any());
        verify(fileApi, never()).deleteFileByPath(any());
    }

    @Test
    void testPublish_notExist(){
        assertServiceException(() -> advertisementService.publish(123L), ADVERTISEMENT_NO_EXIST);
    }

    @Test
    void testPublish_endtime_expired() {
        Long adId = 1L;

        AdvertisementDO ad = new AdvertisementDO()
                .setId(adId)
                .setEndTime(LocalDate.now().minusDays(1));

        when(advertisementMapper.selectOne(any(LambdaQueryWrapperX.class))).thenReturn(ad);

        assertServiceException(() -> advertisementService.publish(adId), ADVERTISEMENT_STATUS_ENDTIME_EXPIRED);
    }

    @Test
    void testPublish_success() {
        Long adId = 1L;

        AdvertisementDO ad = new AdvertisementDO()
                .setId(adId)
                .setEndTime(LocalDate.now().plusDays(1));

        // 模拟 validateAdvertisementExists 返回广告
        when(advertisementMapper.selectOne(any())).thenReturn(ad);

        advertisementService.publish(adId);

        // 验证 update 被调用
        verify(advertisementMapper).update(argThat((LambdaUpdateWrapper wrapper) ->
                wrapper.getSqlSet().contains("status")
        ));
    }

    @Test
    void testUpdate_published() {
        AdvertisementSaveReqVO updateReqVO = new AdvertisementSaveReqVO();
        updateReqVO.setStartTime(LocalDate.now());
        updateReqVO.setEndTime(LocalDate.now());

        AdvertisementDO exist = new AdvertisementDO();
        exist.setStatus(AdvertisementStatusEnum.PUBLISHED.getStatus());
        when(advertisementMapper.selectOne(any()))
                .thenReturn(exist);

        assertServiceException(() -> advertisementService.update(updateReqVO), ADVERTISEMENT_UPDATE_STATUS_ERROR);
    }

    @Test
    void testDelete_published() {
        AdvertisementDO published = new AdvertisementDO();
        published.setId(1L);
        published.setStatus(AdvertisementStatusEnum.PUBLISHED.getStatus());
        published.setAdImagePath("/img/test.png");

        when(advertisementMapper.selectOne(any()))
                .thenReturn(published);

        assertServiceException(() -> advertisementService.delete(1L), ADVERTISEMENT_NO_DELETE);

        // 不删除 DB
        verify(advertisementMapper, never()).deleteById(any());
        // 不删除文件
        verify(fileApi, never()).deleteFileByPath(any());
    }

    @Test
    void testEndPublish_notExist() {
        assertServiceException(() -> advertisementService.endPublish(null), ADVERTISEMENT_NO_EXIST);
    }

    @Test
    void testGet_notExist() {
        assertServiceException(() -> advertisementService.get(123L), ADVERTISEMENT_NO_EXIST);
    }

    @Test
    void testGet() {
        AdvertisementDO ad = new AdvertisementDO();
        ad.setId(1L);
        ad.setAdName("TestAd");
        ad.setStatus(AdvertisementStatusEnum.UNPUBLISHED.getStatus());

        when(advertisementMapper.selectOne(any()))
                .thenReturn(ad);

        AdvertisementRespVO resp = advertisementService.get(1L);

        assertNotNull(resp);
        assertEquals(ad.getId(), resp.getId());
        assertEquals(ad.getAdName(), resp.getAdName());
        assertEquals(ad.getStatus(), resp.getStatus());
    }

    @Test
    void testGetByPosition() {
        Integer adPosition = 2;

        AdvertisementRespVO vo = new AdvertisementRespVO();
        vo.setId(1L);
        vo.setAdName("测试广告");

        List<AdvertisementRespVO> mockList = List.of(vo);

        when(advertisementMapper.selectAdList(
                eq(adPosition),
                eq(AdvertisementStatusEnum.PUBLISHED.getStatus()),
                any(LocalDate.class)
        )).thenReturn(mockList);

        List<AdvertisementRespVO> result =
                advertisementService.getAdvertisementByPosition(adPosition);

        assertNotNull(result);
        assertEquals(1, result.size());
        assertSame(mockList, result);
    }

    @Test
    void testGetAdvertisementByPosition_empty() {
        Integer adPosition = 1;

        when(advertisementMapper.selectAdList(
                eq(adPosition),
                eq(AdvertisementStatusEnum.PUBLISHED.getStatus()),
                any(LocalDate.class)
        )).thenReturn(Collections.emptyList());

        List<AdvertisementRespVO> result =
                advertisementService.getAdvertisementByPosition(adPosition);

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


    @Test
    void testEndPublish() {
        Long adId = 1L;

        AdvertisementDO ad = new AdvertisementDO().setId(adId);

        // 模拟 validateAdvertisementExists 返回广告
        when(advertisementMapper.selectOne(any())).thenReturn(ad);

        advertisementService.endPublish(adId);

        // 验证 update 被调用
        verify(advertisementMapper).update(argThat((LambdaUpdateWrapper wrapper) ->
                wrapper.getSqlSet().contains("status")
        ));
    }

    @Test
    void testGetPage(){
        AdvertisementPageReqVO reqVO = new AdvertisementPageReqVO();

        AdvertisementDO ad1 = new AdvertisementDO()
                .setId(1L)
                .setAdName("Ad1")
                .setStartTime(LocalDate.of(2026, 1, 15))
                .setEndTime(LocalDate.of(2026, 1, 16));

        AdvertisementDO ad2 = new AdvertisementDO()
                .setId(2L)
                .setAdName("Ad2")
                .setStartTime(LocalDate.of(2026, 1, 17))
                .setEndTime(LocalDate.of(2026, 1, 18));

        PageResult<AdvertisementDO> page = new PageResult<>(List.of(ad1, ad2), 2L);

        when(advertisementMapper.selectPage(reqVO)).thenReturn(page);

        PageResult<AdvertisementRespVO> result = advertisementService.getPage(reqVO);

        assertNotNull(result);
        assertEquals(2, result.getList().size());
        assertEquals(2L, result.getTotal());

        AdvertisementRespVO resp1 = result.getList().get(0);
        assertEquals("2026-01-15T00:00~2026-01-16T00:00", resp1.getLaunchTime());

        AdvertisementRespVO resp2 = result.getList().get(1);
        assertEquals("2026-01-17T00:00~2026-01-18T00:00", resp2.getLaunchTime());
    }

    @Test
    void testGetPage_whenEmpty() {
        AdvertisementPageReqVO reqVO = new AdvertisementPageReqVO();

        PageResult<AdvertisementDO> emptyPage = new PageResult<>(0L); // total = 0
        when(advertisementMapper.selectPage(reqVO)).thenReturn(emptyPage);

        PageResult<AdvertisementRespVO> result = advertisementService.getPage(reqVO);

        assertNotNull(result);
        assertEquals(0, result.getList().size());
        assertEquals(0L, result.getTotal());
    }

    @Test
    void testDelete_success() {
        AdvertisementDO draft = new AdvertisementDO();
        draft.setId(1L);
        draft.setStatus(AdvertisementStatusEnum.UNPUBLISHED.getStatus());
        draft.setAdImagePath("/img/test.png");

        when(advertisementMapper.selectOne(any()))
                .thenReturn(draft);

        // 执行
        advertisementService.delete(1L);

        // 删除数据库
        verify(advertisementMapper).deleteById(1L);

        // 删除文件
        verify(fileApi).deleteFileByPath("/img/test.png");
    }

    @Test
    void testRepublish_notExist() {
        AdvertisementRePublishReqVO reqVO = new AdvertisementRePublishReqVO();
        reqVO.setId(null);
        assertServiceException(() -> advertisementService.republish(reqVO), ADVERTISEMENT_NO_EXIST);
    }

    @Test
    void testRepublish_time_error() {
        Long id = 1L;

        AdvertisementRePublishReqVO reqVO = new AdvertisementRePublishReqVO();
        reqVO.setId(id);
        reqVO.setStartTime(LocalDate.now().plusDays(5));
        reqVO.setEndTime(LocalDate.now());

        // 广告存在
        when(advertisementMapper.selectOne(any(LambdaQueryWrapperX.class)))
                .thenReturn(new AdvertisementDO().setId(id));

        assertServiceException(() -> advertisementService.republish(reqVO), ADVERTISEMENT_TIME_ERROR);

        verify(advertisementMapper, never()).update(any());
    }

    @Test
    void testRepublish_success() {
        Long id = 1L;

        AdvertisementRePublishReqVO reqVO = new AdvertisementRePublishReqVO();
        reqVO.setId(id);
        reqVO.setStartTime(LocalDate.now());
        reqVO.setEndTime(LocalDate.now().plusDays(3));

        // mock 广告存在
        when(advertisementMapper.selectOne(any(LambdaQueryWrapperX.class)))
                .thenReturn(new AdvertisementDO().setId(id));

        advertisementService.republish(reqVO);

        verify(advertisementMapper).update(argThat((LambdaUpdateWrapper wrapper) ->
                wrapper.getSqlSet().contains("status")
                && wrapper.getSqlSet().contains("start_time")
                && wrapper.getSqlSet().contains("end_time")
        ));
    }

    @Test
    void testUploadAdImage_success() throws IOException {
        // 模拟文件
        MultipartFile file = mock(MultipartFile.class);
        when(file.isEmpty()).thenReturn(false);
        when(file.getBytes()).thenReturn("test".getBytes());

        // 模拟 fileApi 返回路径
        when(fileApi.createFile(any(byte[].class))).thenReturn("/path/to/file.jpg");

        String result = advertisementService.uploadAdImage(file);

        assertEquals("/path/to/file.jpg", result);
        verify(fileApi, times(1)).createFile(any(byte[].class));
    }

    @Test
    void testUploadAdImage_emptyFile() {
        MultipartFile file = mock(MultipartFile.class);
        when(file.isEmpty()).thenReturn(true);

        assertServiceException(() -> advertisementService.uploadAdImage(file), ADVERTISEMENT_IMAGE_EMPTY_ERROR);

        verify(fileApi, never()).createFile(any(byte[].class));
    }

    @Test
    void testUploadAdImage_ioException() throws IOException {
        MultipartFile file = mock(MultipartFile.class);
        // 模拟非空文件
        when(file.isEmpty()).thenReturn(false);

        // 模拟 getBytes() 抛出 IOException
        when(file.getBytes()).thenThrow(new IOException("io error"));

        assertServiceException(() -> advertisementService.uploadAdImage(file), ADVERTISEMENT_IMAGE_UPLOAD_FAIL);

        verify(fileApi, never()).createFile(any(byte[].class));
    }
}
