package com.sino.mci.send.mq;

import com.sino.mci.channel.adapter.registry.ChannelAdapterRegistry;
import com.sino.mci.channel.common.dto.SendMessageRequest;
import com.sino.mci.channel.common.dto.SendMessageResult;
import com.sino.mci.channel.common.model.ChannelType;
import com.sino.mci.channel.common.spi.ChannelAdapter;
import com.sino.mci.policy.engine.RetryConfig;
import com.sino.mci.send.entity.SendRecord;
import com.sino.mci.send.repository.SendRecordMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.util.Arrays;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@SpringBootTest
@TestPropertySource(properties = {
        "spring.rabbitmq.listener.simple.auto-startup=false",
        "spring.main.allow-bean-definition-overriding=true"
})
@Transactional
class SendMessageConsumerTest {

    @Autowired
    private SendMessageConsumer consumer;

    @Autowired
    private SendRecordMapper sendRecordMapper;

    @Autowired
    private ChannelAdapterRegistry adapterRegistry;

    @Autowired
    private SendMessageProducer sendMessageProducer;

    @Autowired
    private RestTemplate restTemplate;

    private ChannelAdapter adapter;

    @BeforeEach
    void setUp() {
        reset(adapterRegistry, sendMessageProducer, restTemplate);
        adapter = mock(ChannelAdapter.class);
        when(adapter.getChannelType()).thenReturn(ChannelType.WECHAT_PERSONAL);
        when(adapterRegistry.getAdapter(ChannelType.WECHAT_PERSONAL)).thenReturn(adapter);
        when(restTemplate.postForEntity(any(String.class), any(), eq(String.class)))
                .thenReturn(ResponseEntity.ok("ok"));
    }

    @Test
    void successfulSendTriggersCallbackWithExpectedPayloadStructure() {
        SendRecord record = createRecord();
        SendRecordMessage message = createMessage(record, null);

        SendMessageResult successResult = new SendMessageResult();
        successResult.setSuccess(true);
        successResult.setChannelMessageId("msg-ok-001");
        when(adapter.sendMessage(any(SendMessageRequest.class))).thenReturn(successResult);

        consumer.handle(message);

        SendRecord updated = sendRecordMapper.selectById(record.getId());
        assertEquals(2, updated.getSendStatus().intValue());

        ArgumentCaptor<HttpEntity> captor = ArgumentCaptor.forClass(HttpEntity.class);
        verify(restTemplate).postForEntity(eq("http://example.com/callback"), captor.capture(), eq(String.class));
        @SuppressWarnings("unchecked")
        Map<String, Object> payload = (Map<String, Object>) captor.getValue().getBody();
        assertEquals(record.getTaskId(), payload.get("task_id"));
        assertEquals(record.getId(), payload.get("record_id"));
        assertEquals(record.getConversationId(), payload.get("conversation_id"));
        assertEquals(record.getPersonId(), payload.get("person_id"));
        assertEquals("wechat_personal", payload.get("channel_type"));
        assertEquals("delivered", payload.get("event"));
        assertEquals(1, payload.get("status"));
        assertNotNull(payload.get("channel_result"));
        assertNotNull(payload.get("event_time"));

        verify(sendMessageProducer, never()).publishRetryMessage(any(), anyInt());
    }

    @Test
    void failedSendWithRetryPublishesRetryMessage() {
        SendRecord record = createRecord();
        record.setMaxRetry(3);
        sendRecordMapper.updateById(record);

        RetryConfig retryPlan = new RetryConfig(3, Arrays.asList(1, 2, 3));
        SendRecordMessage message = createMessage(record, retryPlan);

        SendMessageResult failureResult = new SendMessageResult();
        failureResult.setSuccess(false);
        failureResult.setMessage("network error");
        when(adapter.sendMessage(any(SendMessageRequest.class))).thenReturn(failureResult);

        consumer.handle(message);

        SendRecord updated = sendRecordMapper.selectById(record.getId());
        assertEquals(3, updated.getSendStatus().intValue());
        assertEquals(1, updated.getRetryCount().intValue());
        assertNotNull(updated.getNextRetryTime());

        ArgumentCaptor<SendRecordMessage> retryCaptor = ArgumentCaptor.forClass(SendRecordMessage.class);
        verify(sendMessageProducer).publishRetryMessage(retryCaptor.capture(), eq(1));
        SendRecordMessage retryMessage = retryCaptor.getValue();
        assertEquals(record.getId(), retryMessage.getRecordId());
        assertEquals(1, retryMessage.getAttempt());
        assertEquals(retryPlan, retryMessage.getRetryPlan());

        verify(restTemplate, never()).postForEntity(any(String.class), any(), eq(String.class));
    }

    @Test
    void maxRetryExceededTriggersFinalCallback() {
        SendRecord record = createRecord();
        record.setRetryCount(4);
        record.setMaxRetry(3);
        sendRecordMapper.updateById(record);

        RetryConfig retryPlan = new RetryConfig(3, Arrays.asList(1, 2, 3));
        SendRecordMessage message = createMessage(record, retryPlan);

        consumer.handle(message);

        SendRecord updated = sendRecordMapper.selectById(record.getId());
        assertEquals(3, updated.getSendStatus().intValue());

        ArgumentCaptor<HttpEntity> captor = ArgumentCaptor.forClass(HttpEntity.class);
        verify(restTemplate).postForEntity(eq("http://example.com/callback"), captor.capture(), eq(String.class));
        @SuppressWarnings("unchecked")
        Map<String, Object> payload = (Map<String, Object>) captor.getValue().getBody();
        assertEquals(record.getId(), payload.get("record_id"));
        assertEquals("failed", payload.get("event"));
        assertEquals(3, payload.get("status"));

        verify(sendMessageProducer, never()).publishRetryMessage(any(), anyInt());
        verify(adapter, never()).sendMessage(any());
    }

    private SendRecord createRecord() {
        SendRecord record = new SendRecord();
        record.setTaskId("SNDTEST001");
        record.setTenantCode("test-tenant");
        record.setTargetType("person");
        record.setTargetValue("{\"person_id\":2001}");
        record.setPersonId(2001L);
        record.setAccountId(100L);
        record.setChannelType("wechat_personal");
        record.setChannelStrategy("{\"channel_type\":\"wechat_personal\",\"strategy\":\"primary\"}");
        record.setContentType("text");
        record.setContentBody("{\"text\":\"hello\"}");
        record.setCallbackUrl("http://example.com/callback");
        record.setSendStatus(0);
        record.setRetryCount(0);
        record.setMaxRetry(0);
        sendRecordMapper.insert(record);
        return record;
    }

    private SendRecordMessage createMessage(SendRecord record, RetryConfig retryPlan) {
        return SendRecordMessage.builder()
                .recordId(record.getId())
                .taskId(record.getTaskId())
                .tenantCode(record.getTenantCode())
                .attempt(record.getRetryCount() != null ? record.getRetryCount() : 0)
                .retryPlan(retryPlan)
                .build();
    }

    @TestConfiguration
    static class MockConfig {

        @Bean
        public ChannelAdapterRegistry channelAdapterRegistry() {
            return mock(ChannelAdapterRegistry.class);
        }

        @Bean
        public SendMessageProducer sendMessageProducer() {
            return mock(SendMessageProducer.class);
        }

        @Bean
        public RestTemplate restTemplate() {
            return mock(RestTemplate.class);
        }
    }
}
