package cn.iocoder.yudao.module.iot.service.device.message;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.NewIotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.*;

/**
 * {@link IotDeviceMessageServiceImpl} 的单元测试
 *
 * 注：TDengine 数据源没有 embedded 替代，mapper 与依赖 service 走 mock；
 * handleUpstreamDeviceMessage 与 sendDeviceMessage 下行成功路径依赖 SpringUtil.getBean 的自调用
 * createDeviceLogAsync，更适合放到集成测试，本类不展开。
 *
 * @author 芋道源码
 */
public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest {

    @InjectMocks
    private IotDeviceMessageServiceImpl service;

    @Mock
    private IotDeviceService deviceService;
    @Mock
    private IotDevicePropertyService devicePropertyService;
    @Mock
    private IotOtaTaskRecordService otaTaskRecordService;
    @Mock
    private IotDeviceMessageMapper deviceMessageMapper;
    @Mock
    private IotDeviceMessageProducer deviceMessageProducer;

    // ========== defineDeviceMessageStable ==========

    @Test
    public void testDefineDeviceMessageStable_whenTableExists_skipCreate() {
        // 准备：showSTable 返回非空 → 表已存在
        when(deviceMessageMapper.showSTable()).thenReturn("device_message");

        // 调用
        service.defineDeviceMessageStable();

        // 断言：跳过 createSTable
        verify(deviceMessageMapper, never()).createSTable();
    }

    @Test
    public void testDefineDeviceMessageStable_whenTableMissing_create() {
        // 准备：showSTable 返回空 → 表不存在
        when(deviceMessageMapper.showSTable()).thenReturn("");

        // 调用
        service.defineDeviceMessageStable();

        // 断言：触发 createSTable
        verify(deviceMessageMapper, times(1)).createSTable();
    }

    // ========== createDeviceLogAsync ==========

    @Test
    public void testCreateDeviceLogAsync_tsFallback_whenNull() {
        // 准备：构造一条 ts 为 null 的消息
        IotDeviceMessage message = buildMessage(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
        long before = System.currentTimeMillis();

        // 调用
        service.createDeviceLogAsync(message);
        long after = System.currentTimeMillis();

        // 断言：mapper.insert 接收到的 messageDO 已被填上 ts，值在 [before, after] 区间
        ArgumentCaptor<IotDeviceMessageDO> captor = ArgumentCaptor.forClass(IotDeviceMessageDO.class);
        verify(deviceMessageMapper).insert(captor.capture());
        Long actualTs = captor.getValue().getTs();
        assertNotNull(actualTs, "ts 不应为空");
        assertTrue(actualTs >= before && actualTs <= after,
                "ts 应在调用前后区间内； 实际 = " + actualTs);
    }

    @Test
    public void testCreateDeviceLogAsync_swallowMapperException() {
        // 准备：mapper.insert 抛异常，验证 @Async 方法内部 try/catch 兜底，不向上抛
        IotDeviceMessage message = buildMessage(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
        doThrow(new RuntimeException("DB unavailable")).when(deviceMessageMapper).insert(any());

        // 调用 & 断言
        assertDoesNotThrow(() -> service.createDeviceLogAsync(message));
        verify(deviceMessageMapper).insert(any(IotDeviceMessageDO.class));
    }

    // ========== sendDeviceMessage ==========

    @Test
    public void testSendDeviceMessage_upstream_publishToProducer() {
        // 准备：上行消息（PROPERTY_POST）
        NewIotDeviceDO device = buildDevice();
        IotDeviceMessage message = buildMessage(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());

        // 调用
        IotDeviceMessage result = service.sendDeviceMessage(message, device);

        // 断言：走 producer.sendDeviceMessage，不进入下行链路
        assertSame(message, result);
        verify(deviceMessageProducer, times(1)).sendDeviceMessage(message);
        verify(deviceMessageProducer, never()).sendDeviceMessageToGateway(any(), any());
        verify(devicePropertyService, never()).getDeviceServerId(any());
    }

    @Test
    public void testSendDeviceMessage_downstream_serverIdMissing_throwException() {
        // 准备：下行消息（SERVICE_INVOKE）；devicePropertyService 也查不到 serverId
        NewIotDeviceDO device = buildDevice();
        IotDeviceMessage message = buildMessage(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod());
        when(devicePropertyService.getDeviceServerId(device.getId())).thenReturn(null);

        // 调用 & 断言：抛 DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL
        ServiceException ex = assertThrows(ServiceException.class,
                () -> service.sendDeviceMessage(message, device));
        assertEquals(1_050_003_007, ex.getCode().intValue());
        verify(deviceMessageProducer, never()).sendDeviceMessageToGateway(any(), any());
    }

    // ========== getDeviceMessagePage ==========

    @Test
    public void testGetDeviceMessagePage_normal() {
        // 准备
        IotDeviceMessagePageReqVO reqVO = new IotDeviceMessagePageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(10);
        IotDeviceMessageDO record = new IotDeviceMessageDO().setId("msg-1");
        Page<IotDeviceMessageDO> page = new Page<>(1, 10, 1L);
        page.setRecords(Collections.singletonList(record));
        when(deviceMessageMapper.selectPage(any(), eq(reqVO))).thenReturn(page);

        // 调用
        PageResult<IotDeviceMessageDO> result = service.getDeviceMessagePage(reqVO);

        // 断言
        assertEquals(1L, result.getTotal());
        assertEquals(1, result.getList().size());
        assertEquals("msg-1", result.getList().get(0).getId());
    }

    @Test
    public void testGetDeviceMessagePage_whenTableMissing_returnEmpty() {
        // 准备：mapper 抛 "Table does not exist" → 视为表未创建，返回空结果
        IotDeviceMessagePageReqVO reqVO = new IotDeviceMessagePageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(10);
        when(deviceMessageMapper.selectPage(any(), any()))
                .thenThrow(new RuntimeException("Table does not exist"));

        // 调用
        PageResult<IotDeviceMessageDO> result = service.getDeviceMessagePage(reqVO);

        // 断言
        assertEquals(0L, result.getTotal());
        assertTrue(result.getList().isEmpty());
    }

    @Test
    public void testGetDeviceMessagePage_otherException_rethrow() {
        // 准备：mapper 抛非「表不存在」的异常 → 应向上抛
        IotDeviceMessagePageReqVO reqVO = new IotDeviceMessagePageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(10);
        when(deviceMessageMapper.selectPage(any(), any()))
                .thenThrow(new RuntimeException("Connection refused"));

        // 调用 & 断言
        assertThrows(RuntimeException.class, () -> service.getDeviceMessagePage(reqVO));
    }

    // ========== getDeviceMessageListByRequestIdsAndReply ==========

    @Test
    public void testGetDeviceMessageListByRequestIdsAndReply_emptyIds_returnEmpty() {
        // 调用 & 断言：requestIds 为空直接返回空列表，不查 DB
        List<IotDeviceMessageDO> result = service.getDeviceMessageListByRequestIdsAndReply(
                1L, Collections.emptyList(), true);

        assertTrue(result.isEmpty());
        verify(deviceMessageMapper, never()).selectListByRequestIdsAndReply(any(), any(), any());
    }

    @Test
    public void testGetDeviceMessageListByRequestIdsAndReply_normal_delegateToMapper() {
        // 准备
        List<String> requestIds = Collections.singletonList("req-1");
        IotDeviceMessageDO record = new IotDeviceMessageDO().setId("msg-1");
        when(deviceMessageMapper.selectListByRequestIdsAndReply(1L, requestIds, true))
                .thenReturn(Collections.singletonList(record));

        // 调用
        List<IotDeviceMessageDO> result = service.getDeviceMessageListByRequestIdsAndReply(
                1L, requestIds, true);

        // 断言
        assertEquals(1, result.size());
        assertEquals("msg-1", result.get(0).getId());
    }

    // ========== getDeviceMessageCount ==========

    @Test
    public void testGetDeviceMessageCount_whenCreateTimeNull_passNullToMapper() {
        // 准备
        when(deviceMessageMapper.selectCountByCreateTime(isNull())).thenReturn(123L);

        // 调用
        Long count = service.getDeviceMessageCount(null);

        // 断言
        assertEquals(123L, count);
        verify(deviceMessageMapper).selectCountByCreateTime(isNull());
    }

    @Test
    public void testGetDeviceMessageCount_withCreateTime_passEpochMilli() {
        // 准备：service 内部用 LocalDateTimeUtil.toEpochMilli 做转换，断言时也用同函数得到期望值
        LocalDateTime createTime = LocalDateTime.of(2026, 1, 1, 0, 0);
        long expectedMs = LocalDateTimeUtil.toEpochMilli(createTime);
        when(deviceMessageMapper.selectCountByCreateTime(expectedMs)).thenReturn(456L);

        // 调用
        Long count = service.getDeviceMessageCount(createTime);

        // 断言
        assertEquals(456L, count);
    }

    // ========== 辅助方法 ==========

    /** 构造一条最简消息（指定 method 决定上下行分支） */
    private IotDeviceMessage buildMessage(String method) {
        IotDeviceMessage message = new IotDeviceMessage();
        message.setId("msg-1");
        message.setDeviceId(2L);
        message.setMethod(method);
        message.setParams(new HashMap<>());
        return message;
    }

    /** 构造最简设备 */
    private NewIotDeviceDO buildDevice() {
        return NewIotDeviceDO.builder().id(2L).build();
    }

}
