package cn.nianxx.thhotel.integrations.ai.superagent.service.impl;

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

import cn.nianxx.thhotel.integrations.ai.superagent.common.dto.SuperAgentDispatchRunDraft;
import cn.nianxx.thhotel.integrations.ai.superagent.common.dto.SuperAgentDispatchRunSnapshot;
import cn.nianxx.thhotel.integrations.ai.superagent.common.dto.SuperAgentDispatchRunSuccessUpdate;
import cn.nianxx.thhotel.integrations.ai.superagent.common.enums.SuperAgentDispatchSource;
import cn.nianxx.thhotel.integrations.ai.superagent.common.enums.SuperAgentDispatchStatus;
import cn.nianxx.thhotel.integrations.ai.superagent.common.request.SuperAgentOpenApiMessageRequest;
import cn.nianxx.thhotel.integrations.ai.superagent.common.result.SuperAgentOpenApiResult;
import cn.nianxx.thhotel.integrations.ai.superagent.repository.SuperAgentDispatchRunRepository;
import cn.nianxx.thhotel.integrations.ai.superagent.service.SuperAgentOpenApiClient;
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageCommand;
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageCaptureResult;
import cn.nianxx.thhotel.platform.message.repository.SourceMessageInboxRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

class SuperAgentDispatchServiceImplTest {

    @Test
    void shouldCreatePendingDispatchForReceivedAgentBusSourceMessage() {
        SuperAgentDispatchRunRepository runRepository = mock(SuperAgentDispatchRunRepository.class);
        when(runRepository.insertIfAbsent(any(SuperAgentDispatchRunDraft.class))).thenReturn(99001L);
        SuperAgentDispatchServiceImpl service = service(runRepository, mock(SourceMessageInboxRepository.class),
                mock(SuperAgentOpenApiClient.class), properties(true, false));

        service.enqueueAgentBusRealtime(agentBusCommand("AGENTBUS"), new SourceMessageCaptureResult(
                88001L,
                true,
                false,
                "RECEIVED"));

        ArgumentCaptor<SuperAgentDispatchRunDraft> captor = ArgumentCaptor.forClass(SuperAgentDispatchRunDraft.class);
        verify(runRepository).insertIfAbsent(captor.capture());
        assertThat(captor.getValue().sourceMessageId()).isEqualTo(88001L);
        assertThat(captor.getValue().dispatchSource()).isEqualTo(SuperAgentDispatchSource.AGENTBUS_REALTIME.code());
        assertThat(captor.getValue().dispatchStatus()).isEqualTo(SuperAgentDispatchStatus.PENDING.code());
        assertThat(captor.getValue().idempotencyKey()).isEqualTo("agentbus-source-message-88001");
    }

    @Test
    void shouldIgnoreNonAgentBusOrFailedCaptureWhenEnqueueDispatch() {
        SuperAgentDispatchRunRepository runRepository = mock(SuperAgentDispatchRunRepository.class);
        SuperAgentDispatchServiceImpl service = service(runRepository, mock(SourceMessageInboxRepository.class),
                mock(SuperAgentOpenApiClient.class), properties(true, false));

        service.enqueueAgentBusRealtime(agentBusCommand("DEBUG_EML_UPLOAD"), new SourceMessageCaptureResult(
                88002L,
                true,
                false,
                "RECEIVED"));
        service.enqueueAgentBusRealtime(agentBusCommand("AGENTBUS"), new SourceMessageCaptureResult(
                88003L,
                true,
                false,
                "FAILED"));

        verify(runRepository, never()).insertIfAbsent(any(SuperAgentDispatchRunDraft.class));
    }

    @Test
    void shouldCreateDispatchForDuplicateReceivedSourceMessageToCompensateMissingOutbox() {
        SuperAgentDispatchRunRepository runRepository = mock(SuperAgentDispatchRunRepository.class);
        when(runRepository.insertIfAbsent(any(SuperAgentDispatchRunDraft.class))).thenReturn(99002L);
        SuperAgentDispatchServiceImpl service = service(runRepository, mock(SourceMessageInboxRepository.class),
                mock(SuperAgentOpenApiClient.class), properties(true, false));

        service.enqueueAgentBusRealtime(agentBusCommand("AGENTBUS"), new SourceMessageCaptureResult(
                88004L,
                false,
                false,
                "RECEIVED"));

        ArgumentCaptor<SuperAgentDispatchRunDraft> captor = ArgumentCaptor.forClass(SuperAgentDispatchRunDraft.class);
        verify(runRepository).insertIfAbsent(captor.capture());
        assertThat(captor.getValue().sourceMessageId()).isEqualTo(88004L);
        assertThat(captor.getValue().dispatchSource()).isEqualTo(SuperAgentDispatchSource.AGENTBUS_REALTIME.code());
    }

    @Test
    void shouldClaimExpiredRunningDispatchesForCrashRecovery() {
        SuperAgentDispatchRunRepository runRepository = mock(SuperAgentDispatchRunRepository.class);
        when(runRepository.claimDue(any(), any(), any(), any(), anyInt())).thenReturn(List.of());
        SuperAgentDispatchServiceImpl service = service(runRepository, mock(SourceMessageInboxRepository.class),
                mock(SuperAgentOpenApiClient.class), properties(true, true));

        service.processDueDispatches();

        @SuppressWarnings("unchecked")
        ArgumentCaptor<List<String>> statusesCaptor = ArgumentCaptor.forClass(List.class);
        verify(runRepository).claimDue(any(), statusesCaptor.capture(), any(), any(), anyInt());
        assertThat(statusesCaptor.getValue())
                .contains(
                        SuperAgentDispatchStatus.PENDING.code(),
                        SuperAgentDispatchStatus.RETRYABLE_FAILED.code(),
                        SuperAgentDispatchStatus.RUNNING.code());
    }

    @Test
    void shouldProcessDueDispatchAndPersistSuperAgentSuccess() {
        SuperAgentDispatchRunRepository runRepository = mock(SuperAgentDispatchRunRepository.class);
        SourceMessageInboxRepository sourceMessageRepository = mock(SourceMessageInboxRepository.class);
        SuperAgentOpenApiClient openApiClient = mock(SuperAgentOpenApiClient.class);
        SuperAgentDispatchServiceImpl service = service(runRepository, sourceMessageRepository, openApiClient,
                properties(true, true));
        SuperAgentDispatchRunSnapshot snapshot = snapshot();
        when(runRepository.claimDue(any(), any(), any(), any(), any(Integer.class))).thenReturn(List.of(snapshot));
        when(sourceMessageRepository.findPayloadJson(88001L)).thenReturn(Optional.of("""
                {"source":{"external_message_id":"mail-agentbus-001"},"body":{"text":"Please book a room"}}
                """));
        when(openApiClient.invokeMessage(any(SuperAgentOpenApiMessageRequest.class))).thenReturn(new SuperAgentOpenApiResult(
                "session-dispatch-001",
                "run-dispatch-001",
                "profile-dispatch",
                "version-dispatch",
                "model-dispatch",
                "{\"route_code\":\"S10\"}",
                10,
                5,
                15,
                List.of("trace", "end"),
                List.of(),
                "/api/open/agent-sessions/session-dispatch-001/runs/run-dispatch-001",
                "4"));

        int processed = service.processDueDispatches();

        assertThat(processed).isEqualTo(1);
        ArgumentCaptor<SuperAgentOpenApiMessageRequest> requestCaptor =
                ArgumentCaptor.forClass(SuperAgentOpenApiMessageRequest.class);
        verify(openApiClient).invokeMessage(requestCaptor.capture());
        assertThat(requestCaptor.getValue().idempotencyKey()).isEqualTo("agentbus-source-message-88001");
        assertThat(requestCaptor.getValue().metadata()).containsEntry("external_message_id", "mail-agentbus-001");
        ArgumentCaptor<SuperAgentDispatchRunSuccessUpdate> updateCaptor =
                ArgumentCaptor.forClass(SuperAgentDispatchRunSuccessUpdate.class);
        verify(runRepository).markSucceeded(updateCaptor.capture());
        assertThat(updateCaptor.getValue().id()).isEqualTo(99001L);
        assertThat(updateCaptor.getValue().superagentRunId()).isEqualTo("run-dispatch-001");
        assertThat(updateCaptor.getValue().lastEventId()).isEqualTo("4");
        assertThat(updateCaptor.getValue().superagentParsedJson()).isEqualTo("{\"route_code\":\"S10\"}");
    }

    private SuperAgentDispatchServiceImpl service(
            SuperAgentDispatchRunRepository runRepository,
            SourceMessageInboxRepository sourceMessageRepository,
            SuperAgentOpenApiClient openApiClient,
            SuperAgentDispatchProperties properties) {
        return new SuperAgentDispatchServiceImpl(
                properties,
                new SuperAgentOpenApiProperties(),
                runRepository,
                sourceMessageRepository,
                openApiClient,
                new ObjectMapper());
    }

    private SuperAgentDispatchProperties properties(boolean enabled, boolean workerEnabled) {
        SuperAgentDispatchProperties properties = new SuperAgentDispatchProperties();
        properties.setEnabled(enabled);
        properties.setWorkerEnabled(workerEnabled);
        return properties;
    }

    private CaptureSourceMessageCommand agentBusCommand(String provider) {
        return new CaptureSourceMessageCommand(
                "HOTEL-TEST",
                provider,
                "OUTLOOK",
                "mail-agentbus-001",
                "conversation-agentbus-001",
                "frame-agentbus-001",
                "session-agentbus-001",
                null,
                null,
                "guest@example.test",
                "Booking",
                "Please book a room",
                "<html>Please book a room</html>",
                "{\"source\":{\"external_message_id\":\"mail-agentbus-001\"}}",
                "agentbus-outlook-v1",
                List.of());
    }

    private SuperAgentDispatchRunSnapshot snapshot() {
        LocalDateTime now = LocalDateTime.of(2026, 7, 12, 12, 0);
        return new SuperAgentDispatchRunSnapshot(
                99001L,
                "HOTEL-TEST",
                88001L,
                "AGENTBUS",
                "OUTLOOK",
                "mail-agentbus-001",
                "conversation-agentbus-001",
                SuperAgentDispatchSource.AGENTBUS_REALTIME.code(),
                "agentbus-source-message-88001",
                "agentbus-dispatch-source-88001",
                SuperAgentDispatchStatus.RUNNING.code(),
                1,
                3,
                now,
                now.plusMinutes(5),
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                now,
                now);
    }
}
