package cn.nianxx.thhotel.integrations.messaging.agentbus.adapter;

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

import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageCommand;
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageCaptureResult;
import cn.nianxx.thhotel.platform.message.service.SourceMessageCaptureService;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

class AgentBusFrameProcessorTest {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void shouldIgnoreSessionReadyAndMarkStatusWithoutCapturing() {
        SourceMessageCaptureService captureService = mock(SourceMessageCaptureService.class);
        AgentBusConnectionStatus status = new AgentBusConnectionStatus();
        AgentBusFrameProcessor processor = processor(captureService, status, properties(true, 1024));

        AgentBusFrameProcessResult result = processor.process("""
                {"type":"session.ready","session_id":"session-agentbus-001"}
                """);

        assertThat(result.outcome()).isEqualTo("IGNORED");
        assertThat(status.snapshot().sessionReady()).isTrue();
        assertThat(status.snapshot().ignoredFrameCount()).isEqualTo(1L);
        verifyNoInteractions(captureService);
    }

    @Test
    void shouldCaptureBusinessFrameIntoSourceMessageInbox() {
        SourceMessageCaptureService captureService = mock(SourceMessageCaptureService.class);
        when(captureService.capture(any(CaptureSourceMessageCommand.class)))
                .thenReturn(new SourceMessageCaptureResult(88001L, true, false, "RECEIVED"));
        AgentBusConnectionStatus status = new AgentBusConnectionStatus();
        AgentBusFrameProcessor processor = processor(captureService, status, properties(true, 4096));

        AgentBusFrameProcessResult result = processor.process("""
                {
                  "id": "frame-agentbus-capture-001",
                  "session_id": "session-agentbus-capture",
                  "payload": {
                    "body": {"text": "Please update my reservation.", "html": "<html>Reservation</html>"},
                    "source": {
                      "channel": "email",
                      "external_message_id": "mail-agentbus-capture-001",
                      "external_conversation_id": "conversation-agentbus-capture",
                      "sender": "guest@example.test",
                      "subject": "Reservation update"
                    }
                  }
                }
                """);

        assertThat(result.outcome()).isEqualTo("CAPTURED");
        assertThat(result.inboxId()).isEqualTo(88001L);
        assertThat(status.snapshot().capturedFrameCount()).isEqualTo(1L);
        ArgumentCaptor<CaptureSourceMessageCommand> captor = ArgumentCaptor.forClass(CaptureSourceMessageCommand.class);
        verify(captureService).capture(captor.capture());
        assertThat(captor.getValue().hotelId()).isEqualTo("HOTEL-SYSTEM");
        assertThat(captor.getValue().externalMessageId()).isEqualTo("mail-agentbus-capture-001");
        assertThat(captor.getValue().providerFrameId()).isEqualTo("frame-agentbus-capture-001");
    }

    @Test
    void shouldIgnoreBusinessFrameWhenCaptureDisabled() {
        SourceMessageCaptureService captureService = mock(SourceMessageCaptureService.class);
        AgentBusConnectionStatus status = new AgentBusConnectionStatus();
        AgentBusFrameProcessor processor = processor(captureService, status, properties(false, 4096));

        AgentBusFrameProcessResult result = processor.process("""
                {
                  "payload": {
                    "body": {"text": "Ignored body"},
                    "source": {"external_message_id": "mail-agentbus-disabled-001"}
                  }
                }
                """);

        assertThat(result.outcome()).isEqualTo("IGNORED");
        assertThat(status.snapshot().ignoredFrameCount()).isEqualTo(1L);
        verifyNoInteractions(captureService);
    }

    @Test
    void shouldRejectOversizedFrameWithoutCapturing() {
        SourceMessageCaptureService captureService = mock(SourceMessageCaptureService.class);
        AgentBusConnectionStatus status = new AgentBusConnectionStatus();
        AgentBusFrameProcessor processor = processor(captureService, status, properties(true, 10));

        AgentBusFrameProcessResult result = processor.process("{\"type\":\"business\",\"payload\":{\"text\":\"too-large\"}}");

        assertThat(result.outcome()).isEqualTo("REJECTED");
        assertThat(status.snapshot().rejectedFrameCount()).isEqualTo(1L);
        assertThat(status.snapshot().lastErrorCode()).isEqualTo("FRAME_TOO_LARGE");
        verifyNoInteractions(captureService);
    }

    private AgentBusFrameProcessor processor(
            SourceMessageCaptureService captureService,
            AgentBusConnectionStatus status,
            AgentBusProperties properties) {
        HotelContextService hotelContextService = mock(HotelContextService.class);
        when(hotelContextService.resolveSystemHotelId()).thenReturn("HOTEL-SYSTEM");
        return new AgentBusFrameProcessor(
                objectMapper,
                new AgentBusSourceMessageAdapter(objectMapper),
                captureService,
                status,
                properties,
                hotelContextService);
    }

    private AgentBusProperties properties(boolean captureEnabled, int maxFrameBytes) {
        AgentBusProperties properties = new AgentBusProperties();
        properties.getCapture().setEnabled(captureEnabled);
        properties.setMaxFrameBytes(maxFrameBytes);
        return properties;
    }
}
