package org.dromara.app.handler;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dromara.app.domain.AppSchedulingDevice;
import org.dromara.app.domain.bo.AppDeviceBo;
import org.dromara.app.domain.bo.AppWateringLogBo;
import org.dromara.app.domain.vo.AppDeviceVo;
import org.dromara.app.domain.vo.AppWateringLogVo;
import org.dromara.app.mapper.AppSchedulingDeviceMapper;
import org.dromara.app.service.IAppDeviceService;
import org.dromara.app.service.IAppWateringLogService;
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.junit.jupiter.MockitoExtension;
import org.springframework.context.support.GenericApplicationContext;

import java.text.SimpleDateFormat;
import java.util.List;
import java.util.function.Supplier;

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

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

    @Mock private IAppWateringLogService appWateringLogService;
    @Mock private IAppDeviceService appDeviceService;
    @Mock private AppSchedulingDeviceMapper schedulingDeviceMapper;
    @Mock private DeviceIdentityResolver deviceIdentityResolver;

    @Test
    void handle_insertsRunningScheduleWateringLog() throws Exception {
        StartWaterHandler handler = new StartWaterHandler(
            appWateringLogService,
            appDeviceService,
            schedulingDeviceMapper,
            deviceIdentityResolver
        );
        AppDeviceVo device = new AppDeviceVo();
        device.setUserId(99L);
        AppSchedulingDevice binding = new AppSchedulingDevice();
        binding.setDeviceNo("D01");
        binding.setScheduleId(10L);

        when(deviceIdentityResolver.resolveDeviceNo("D01")).thenReturn("D01");
        when(appDeviceService.queryById("D01")).thenReturn(device);
        when(schedulingDeviceMapper.selectList(any())).thenReturn(List.of(binding));

        withJsonContext(() -> {
            handler.handle("D01", "{\"startTime\":\"2026-07-06 08:30:00\",\"triggerON\":\"schedule\"}");
            return null;
        });

        ArgumentCaptor<AppWateringLogBo> captor = ArgumentCaptor.forClass(AppWateringLogBo.class);
        verify(appWateringLogService).insertByBo(captor.capture());
        AppWateringLogBo logBo = captor.getValue();
        assertThat(logBo.getDeviceNo()).isEqualTo("D01");
        assertThat(logBo.getUserId()).isEqualTo(99L);
        assertThat(logBo.getScheduleId()).isEqualTo(10L);
        assertThat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(logBo.getStartTime()))
            .isEqualTo("2026-07-06 08:30:00");
        assertThat(logBo.getTriggerType()).isEqualTo("0");
        assertThat(logBo.getStatus()).isEqualTo("1");
        assertThat(logBo.getEndTime()).isNull();
        assertThat(logBo.getRemark()).isEqualTo("设备上报排程开始浇水");

        ArgumentCaptor<AppDeviceBo> deviceCaptor = ArgumentCaptor.forClass(AppDeviceBo.class);
        verify(appDeviceService).updateByBo(deviceCaptor.capture());
        AppDeviceBo deviceBo = deviceCaptor.getValue();
        assertThat(deviceBo.getDeviceNo()).isEqualTo("D01");
        assertThat(deviceBo.getWorkStatus()).isEqualTo("1");
    }

    @Test
    void handle_skipsDuplicateRunningWateringLogWithSameStartTime() throws Exception {
        StartWaterHandler handler = new StartWaterHandler(
            appWateringLogService,
            appDeviceService,
            schedulingDeviceMapper,
            deviceIdentityResolver
        );
        AppDeviceVo device = new AppDeviceVo();
        device.setUserId(99L);
        AppWateringLogVo activeLog = new AppWateringLogVo();
        activeLog.setDeviceNo("D01");
        activeLog.setUserId(99L);
        activeLog.setTriggerType("1");
        activeLog.setStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2026-07-06 08:30:00"));
        activeLog.setEndTime(null);

        when(deviceIdentityResolver.resolveDeviceNo("D01")).thenReturn("D01");
        when(appDeviceService.queryById("D01")).thenReturn(device);
        lenient().when(appWateringLogService.queryList(any(AppWateringLogBo.class))).thenReturn(List.of(activeLog));

        withJsonContext(() -> {
            handler.handle("D01", "{\"startTime\":\"2026-07-06 08:30:00\",\"triggerON\":\"manual\"}");
            return null;
        });

        verify(appWateringLogService, never()).insertByBo(any(AppWateringLogBo.class));
        ArgumentCaptor<AppDeviceBo> deviceCaptor = ArgumentCaptor.forClass(AppDeviceBo.class);
        verify(appDeviceService).updateByBo(deviceCaptor.capture());
        assertThat(deviceCaptor.getValue().getDeviceNo()).isEqualTo("D01");
        assertThat(deviceCaptor.getValue().getWorkStatus()).isEqualTo("1");
    }

    @Test
    void handle_skipsDuplicateRunningWateringLogWithinSameMinute() throws Exception {
        StartWaterHandler handler = new StartWaterHandler(
            appWateringLogService,
            appDeviceService,
            schedulingDeviceMapper,
            deviceIdentityResolver
        );
        AppDeviceVo device = new AppDeviceVo();
        device.setUserId(99L);
        AppWateringLogVo activeLog = new AppWateringLogVo();
        activeLog.setDeviceNo("D01");
        activeLog.setUserId(99L);
        activeLog.setScheduleId(0L);
        activeLog.setTriggerType("1");
        activeLog.setStatus("1");
        activeLog.setStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2026-07-06 08:30:00"));
        activeLog.setEndTime(null);

        when(deviceIdentityResolver.resolveDeviceNo("D01")).thenReturn("D01");
        when(appDeviceService.queryById("D01")).thenReturn(device);
        lenient().when(appWateringLogService.queryList(any(AppWateringLogBo.class))).thenReturn(List.of(activeLog));

        withJsonContext(() -> {
            handler.handle("D01", "{\"startTime\":\"2026-07-06 08:30:27\",\"triggerON\":\"manual\"}");
            return null;
        });

        verify(appWateringLogService, never()).insertByBo(any(AppWateringLogBo.class));
        ArgumentCaptor<AppDeviceBo> deviceCaptor = ArgumentCaptor.forClass(AppDeviceBo.class);
        verify(appDeviceService).updateByBo(deviceCaptor.capture());
        assertThat(deviceCaptor.getValue().getDeviceNo()).isEqualTo("D01");
        assertThat(deviceCaptor.getValue().getWorkStatus()).isEqualTo("1");
    }

    @Test
    void handle_setsTodayEndTimeFromHourMinuteStartTimeAndDurationMin() {
        StartWaterHandler handler = new StartWaterHandler(
            appWateringLogService,
            appDeviceService,
            schedulingDeviceMapper,
            deviceIdentityResolver
        );
        AppDeviceVo device = new AppDeviceVo();
        device.setUserId(99L);
        String today = new SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date());

        when(deviceIdentityResolver.resolveDeviceNo("D01")).thenReturn("D01");
        when(appDeviceService.queryById("D01")).thenReturn(device);

        withJsonContext(() -> {
            handler.handle("D01", "{\"deviceNo\":\"D01\",\"startTime\":\"16:39\",\"durationMin\":10,\"triggerON\":\"manual\"}");
            return null;
        });

        ArgumentCaptor<AppWateringLogBo> captor = ArgumentCaptor.forClass(AppWateringLogBo.class);
        verify(appWateringLogService).insertByBo(captor.capture());
        AppWateringLogBo logBo = captor.getValue();
        assertThat(logBo.getDurationMin()).isEqualTo("10");
        assertThat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(logBo.getStartTime()))
            .isEqualTo(today + " 16:39:00");
        assertThat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(logBo.getEndTime()))
            .isEqualTo(today + " 16:49:00");
        assertThat(logBo.getStatus()).isEqualTo("1");

        ArgumentCaptor<AppDeviceBo> deviceCaptor = ArgumentCaptor.forClass(AppDeviceBo.class);
        verify(appDeviceService).updateByBo(deviceCaptor.capture());
        assertThat(deviceCaptor.getValue().getDeviceNo()).isEqualTo("D01");
        assertThat(deviceCaptor.getValue().getWorkStatus()).isEqualTo("1");
    }

    private <T> T withJsonContext(Supplier<T> action) {
        try (GenericApplicationContext applicationContext = new GenericApplicationContext()) {
            applicationContext.registerBean(ObjectMapper.class, (Supplier<ObjectMapper>) ObjectMapper::new);
            applicationContext.refresh();
            new SpringUtil().setApplicationContext(applicationContext);
            return action.get();
        }
    }
}
