package org.dromara.app.task;

import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.session.Configuration;
import org.dromara.app.domain.AppDevice;
import org.dromara.app.handler.MqttDeviceStatusService;
import org.dromara.app.mapper.AppDeviceMapper;
import org.dromara.common.redis.utils.RedisUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.redisson.api.RedissonClient;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.util.ReflectionTestUtils;

import java.time.Duration;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
@Tag("dev")
class DeviceOfflineCheckTaskTest {

    private static GenericApplicationContext applicationContext;

    @Mock
    private AppDeviceMapper appDeviceMapper;
    @Mock
    private MqttDeviceStatusService deviceStatusService;

    @BeforeAll
    static void initializeInfrastructure() {
        if (TableInfoHelper.getTableInfo(AppDevice.class) == null) {
            TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), ""), AppDevice.class);
        }
        applicationContext = new GenericApplicationContext();
        applicationContext.registerBean(RedissonClient.class, () -> mock(RedissonClient.class));
        applicationContext.refresh();
        new SpringUtil().setApplicationContext(applicationContext);
    }

    @AfterAll
    static void closeApplicationContext() {
        applicationContext.close();
    }

    @Test
    void checkOfflineDevicesAddsTtlToLegacyPermanentOnlineKey() {
        DeviceOfflineCheckTask task = newTask();
        AppDevice device = new AppDevice();
        device.setDeviceNo("D01");
        when(appDeviceMapper.selectList(any(Wrapper.class))).thenReturn(List.of(device));

        try (MockedStatic<RedisUtils> redis = mockStatic(RedisUtils.class)) {
            redis.when(() -> RedisUtils.hasKey("mqtt:device:status:D01")).thenReturn(true);
            redis.when(() -> RedisUtils.getTimeToLive("mqtt:device:status:D01")).thenReturn(-1L);

            task.checkOfflineDevices();

            redis.verify(() -> RedisUtils.expire("mqtt:device:status:D01", Duration.ofSeconds(600)));
            verify(deviceStatusService, never()).markOfflineIfCacheMissing(any(), any());
        }
    }

    @Test
    void checkOfflineDevicesMarksDeviceOfflineWhenPowerHeartbeatExpired() {
        DeviceOfflineCheckTask task = newTask();
        AppDevice device = new AppDevice();
        device.setDeviceNo("D01");
        when(appDeviceMapper.selectList(any(Wrapper.class))).thenReturn(List.of(device));

        try (MockedStatic<RedisUtils> redis = mockStatic(RedisUtils.class)) {
            redis.when(() -> RedisUtils.hasKey("mqtt:device:status:D01")).thenReturn(false);

            task.checkOfflineDevices();

            verify(deviceStatusService).markOfflineIfCacheMissing("D01", "设备电量心跳超时");
        }
    }

    private DeviceOfflineCheckTask newTask() {
        DeviceOfflineCheckTask task = new DeviceOfflineCheckTask(appDeviceMapper, deviceStatusService);
        ReflectionTestUtils.setField(task, "deviceStatusCachePrefix", "mqtt:device:status:");
        ReflectionTestUtils.setField(task, "deviceStatusCacheTtlSeconds", 600L);
        ReflectionTestUtils.setField(task, "enabled", true);
        ReflectionTestUtils.setField(task, "ttlCompatEnabled", true);
        return task;
    }
}
