package org.dromara.app.service.impl;

import org.dromara.app.domain.bo.AppWateringLogBo;
import org.dromara.app.domain.mqtt.DeviceCommand;
import org.dromara.app.domain.vo.AppDeviceVo;
import org.dromara.app.service.IAppDeviceService;
import org.dromara.app.service.IAppWateringLogService;
import org.dromara.app.service.IDeviceCommandPublisher;
import org.dromara.common.satoken.utils.LoginHelper;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

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

    @Mock private IAppDeviceService deviceService;
    @Mock private IAppWateringLogService wateringLogService;
    @Mock private IDeviceCommandPublisher commandPublisher;

    @Test
    void sendScheduleBindCommand_buildsScheduleTopicAndPayload() {
        AppDeviceVo device = new AppDeviceVo();
        device.setDeviceNo("D01");
        device.setUserId(99L);
        device.setMacAddress("AABBCC");

        Map<String, Object> payload = new HashMap<>();
        payload.put("cmd", -1);
        payload.put("deviceNo", "D01");

        when(deviceService.queryById("D01")).thenReturn(device);
        when(commandPublisher.send(any(DeviceCommand.class))).thenReturn("cmd-1");

        DeviceCommandServiceImpl service = new DeviceCommandServiceImpl(deviceService, wateringLogService);
        ReflectionTestUtils.setField(service, "commandPublisher", commandPublisher);

        String commandId = service.sendScheduleBindCommand("D01", payload);

        assertThat(commandId).isEqualTo("cmd-1");
        ArgumentCaptor<DeviceCommand> commandCaptor = ArgumentCaptor.forClass(DeviceCommand.class);
        verify(commandPublisher).send(commandCaptor.capture());
        DeviceCommand command = commandCaptor.getValue();
        assertThat(command.getDeviceNo()).isEqualTo("D01");
        assertThat(command.getDeviceMac()).isEqualTo("AABBCC");
        assertThat(command.getCommandType()).isEqualTo("bindSchedule");
        assertThat(command.getTopic()).isEqualTo("/d01/subscriber/schedule");
        assertThat(command.getPayload()).containsEntry("cmd", -1).containsEntry("deviceNo", "D01");
    }

    @Test
    void sendScheduleCanceledCommand_buildsScheduleCanceledTopicAndPayload() {
        AppDeviceVo device = new AppDeviceVo();
        device.setDeviceNo("D01");
        device.setUserId(99L);
        device.setMacAddress("AABBCC");

        when(deviceService.queryById("D01")).thenReturn(device);
        when(commandPublisher.send(any(DeviceCommand.class))).thenReturn("cmd-1");

        DeviceCommandServiceImpl service = new DeviceCommandServiceImpl(deviceService, wateringLogService);
        ReflectionTestUtils.setField(service, "commandPublisher", commandPublisher);

        String commandId = service.sendScheduleCanceledCommand("D01", 10L);

        assertThat(commandId).isEqualTo("cmd-1");
        ArgumentCaptor<DeviceCommand> commandCaptor = ArgumentCaptor.forClass(DeviceCommand.class);
        verify(commandPublisher).send(commandCaptor.capture());
        DeviceCommand command = commandCaptor.getValue();
        assertThat(command.getDeviceNo()).isEqualTo("D01");
        assertThat(command.getDeviceMac()).isEqualTo("AABBCC");
        assertThat(command.getCommandType()).isEqualTo("cancelSchedule");
        assertThat(command.getTopic()).isEqualTo("/d01/schedule/canceled");
        assertThat(command.getPayload())
            .containsEntry("deviceNo", "D01")
            .containsEntry("scheduleId", 10L)
            .containsEntry("canceled", true);
    }

    @Test
    void sendBindDeviceCommand_defaultsTopicToDeviceNo() {
        AppDeviceVo device = new AppDeviceVo();
        device.setDeviceNo("D01");
        device.setUserId(99L);
        device.setMacAddress("AABBCC");

        when(deviceService.queryById("D01")).thenReturn(device);
        when(commandPublisher.send(any(DeviceCommand.class))).thenReturn("cmd-1");

        DeviceCommandServiceImpl service = new DeviceCommandServiceImpl(deviceService, wateringLogService);
        ReflectionTestUtils.setField(service, "commandPublisher", commandPublisher);

        service.sendBindDeviceCommand("D01");

        ArgumentCaptor<DeviceCommand> commandCaptor = ArgumentCaptor.forClass(DeviceCommand.class);
        verify(commandPublisher).send(commandCaptor.capture());
        assertThat(commandCaptor.getValue().getDeviceNo()).isEqualTo("D01");
        assertThat(commandCaptor.getValue().getCommandType()).isEqualTo("bindDevice");
        assertThat(commandCaptor.getValue().getPayload()).containsEntry("deviceNo", "D01");
    }

    @Test
    void sendBindDeviceCommand_keepsMacAsPayloadOnlyForFirstRegistrationFlow() {
        AppDeviceVo device = new AppDeviceVo();
        device.setDeviceNo("D01");
        device.setUserId(99L);
        device.setMacAddress("AABBCC");

        when(deviceService.queryById("D01")).thenReturn(device);
        when(commandPublisher.send(any(DeviceCommand.class))).thenReturn("cmd-1");

        DeviceCommandServiceImpl service = new DeviceCommandServiceImpl(deviceService, wateringLogService);
        ReflectionTestUtils.setField(service, "commandPublisher", commandPublisher);

        service.sendBindDeviceCommand("D01");

        ArgumentCaptor<DeviceCommand> commandCaptor = ArgumentCaptor.forClass(DeviceCommand.class);
        verify(commandPublisher).send(commandCaptor.capture());
        DeviceCommand command = commandCaptor.getValue();
        assertThat(command.getDeviceNo()).isEqualTo("D01");
        assertThat(command.getDeviceMac()).isEqualTo("AABBCC");
        assertThat(command.getCommandType()).isEqualTo("bindDevice");
        assertThat(command.getPayload()).containsEntry("deviceNo", "D01");
    }

    @Test
    void sendSwitchCommand_addsSecondsWhenStartTimeOnlyHasHourAndMinute() {
        AppDeviceVo device = new AppDeviceVo();
        device.setDeviceNo("D01");
        device.setUserId(99L);
        device.setMacAddress("AABBCC");

        when(deviceService.queryById("D01")).thenReturn(device);
        when(commandPublisher.send(any(DeviceCommand.class))).thenReturn("cmd-1");

        DeviceCommandServiceImpl service = new DeviceCommandServiceImpl(deviceService, wateringLogService);
        ReflectionTestUtils.setField(service, "commandPublisher", commandPublisher);

        try (MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
            loginHelper.when(LoginHelper::getUserId).thenReturn(99L);
            service.sendSwitchCommand("D01", "1", "08:05", 10);
        }

        ArgumentCaptor<DeviceCommand> commandCaptor = ArgumentCaptor.forClass(DeviceCommand.class);
        verify(commandPublisher).send(commandCaptor.capture());
        assertThat(commandCaptor.getValue().getPayload()).containsEntry("startTime", "08:05:00");

        ArgumentCaptor<AppWateringLogBo> logCaptor = ArgumentCaptor.forClass(AppWateringLogBo.class);
        verify(wateringLogService).insertByBo(logCaptor.capture());
        AppWateringLogBo logBo = logCaptor.getValue();
        assertThat(new SimpleDateFormat("HH:mm:ss").format(logBo.getStartTime())).isEqualTo("08:05:00");
        assertThat(new SimpleDateFormat("HH:mm:ss").format(logBo.getEndTime())).isEqualTo("08:15:00");
    }

    @Test
    void sendSwitchCommand_stopFinishesCurrentRunningLogForDevice() {
        AppDeviceVo device = new AppDeviceVo();
        device.setDeviceNo("D01");
        device.setUserId(99L);
        device.setMacAddress("AABBCC");

        when(deviceService.queryById("D01")).thenReturn(device);
        when(commandPublisher.send(any(DeviceCommand.class))).thenReturn("cmd-1");
        when(wateringLogService.finishRunningLogsByDevice(any(), any(), any())).thenReturn(1);

        DeviceCommandServiceImpl service = new DeviceCommandServiceImpl(deviceService, wateringLogService);
        ReflectionTestUtils.setField(service, "commandPublisher", commandPublisher);

        try (MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
            loginHelper.when(LoginHelper::getUserId).thenReturn(99L);
            service.sendSwitchCommand("D01", "0", null, null);
        }

        verify(wateringLogService).finishRunningLogsByDevice(eq("D01"), any(), eq("手动停止浇水"));
        verify(wateringLogService, never()).finishManualLog(any(), any(), any(), any());
    }
}
