package org.dromara.app.handler;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dromara.app.domain.AppDevice;
import org.dromara.app.domain.bo.AppDeviceBo;
import org.dromara.app.domain.mqtt.DeviceCommand;
import org.dromara.app.mapper.AppDeviceMapper;
import org.dromara.app.service.IAppDeviceService;
import org.dromara.app.service.IDeviceCommandPublisher;
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.beans.factory.ObjectProvider;
import org.springframework.context.support.GenericApplicationContext;

import java.lang.reflect.Method;
import java.util.function.Supplier;

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

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

    @Mock private IAppDeviceService appDeviceService;
    @Mock private AppDeviceMapper appDeviceMapper;
    @Mock private ObjectProvider<IDeviceCommandPublisher> commandPublisherProvider;
    @Mock private IDeviceCommandPublisher commandPublisher;
    @Mock private DeviceIdentityResolver deviceIdentityResolver;
    @Mock private MqttDeviceStatusService deviceStatusService;

    @Test
    void firstRegistrationReplyStillUsesMacTopic() throws Exception {
        DeviceRegisterHandler handler = new DeviceRegisterHandler(
            appDeviceService,
            appDeviceMapper,
            commandPublisherProvider,
            deviceIdentityResolver,
            deviceStatusService
        );
        when(commandPublisherProvider.getIfAvailable()).thenReturn(commandPublisher);
        when(commandPublisher.send(any(DeviceCommand.class))).thenReturn("cmd-1");

        Method method = DeviceRegisterHandler.class.getDeclaredMethod(
            "sendDeviceNoToDevice", String.class, String.class
        );
        method.setAccessible(true);
        method.invoke(handler, "D01", "AA:BB:CC");

        ArgumentCaptor<DeviceCommand> captor = ArgumentCaptor.forClass(DeviceCommand.class);
        verify(commandPublisher).send(captor.capture());
        assertThat(captor.getValue().getTopic()).isEqualTo("/aa:bb:cc/subscriber/cmd");
        assertThat(captor.getValue().getCommandType()).isEqualTo("registerDeviceNo");
        assertThat(captor.getValue().getDeviceNo()).isEqualTo("D01");
    }

    @Test
    void handleKeepsExistingMacWhenTopicUsesDeviceNoAndPayloadOmitsMac() {
        DeviceRegisterHandler handler = new DeviceRegisterHandler(
            appDeviceService,
            appDeviceMapper,
            commandPublisherProvider,
            deviceIdentityResolver,
            deviceStatusService
        );
        AppDevice existing = new AppDevice();
        existing.setDeviceNo("2075423638947475457");
        existing.setMacAddress("aa:bb:cc");
        when(deviceIdentityResolver.resolve("2075423638947475457")).thenReturn(existing);
        when(commandPublisherProvider.getIfAvailable()).thenReturn(null);

        withJsonContext(() -> {
            handler.handle("2075423638947475457", "{}");
            return null;
        });

        org.mockito.ArgumentCaptor<AppDeviceBo> captor = org.mockito.ArgumentCaptor.forClass(AppDeviceBo.class);
        verify(appDeviceService).registerByMqtt(captor.capture());
        assertThat(captor.getValue().getDeviceNo()).isEqualTo("2075423638947475457");
        assertThat(captor.getValue().getMacAddress()).isEqualTo("aa:bb:cc");
        verify(deviceStatusService).markOnline("2075423638947475457");
    }

    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();
        }
    }
}
