package com.sino.mci.channel.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sino.mci.channel.entity.ChannelSubject;
import com.sino.mci.channel.messaging.RabbitConfig;
import com.sino.mci.channel.messaging.SessionArchivePullCommand;
import com.sino.mci.channel.repository.ChannelSubjectMapper;
import com.sino.mci.channel.service.SessionArchivePullService;
import com.sino.mci.channel.wecom.archive.FinanceSdkClient;
import com.sino.mci.channel.wecom.archive.model.FinanceMessage;
import com.sino.mci.inbound.entity.MessageEvent;
import com.sino.mci.inbound.repository.MessageEventMapper;
import com.sino.mci.message.entity.MessageMedia;
import com.sino.mci.message.repository.MessageMediaMapper;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import static com.sino.mci.test.AuthTestHelper.createTenantAndLogin;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = {
        "spring.rabbitmq.listener.simple.auto-startup=false",
        "spring.main.allow-bean-definition-overriding=true",
        "sino.channel.wecom.session-archive.listener-enabled=false",
        "mci.wecom.sync.mock=true"
})
@Transactional
class WeComSessionArchiveTest {

    private static final String CORP_ID = "ww-session-archive";

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ChannelSubjectMapper channelSubjectMapper;

    @Autowired
    private MessageEventMapper messageEventMapper;

    @Autowired
    private MessageMediaMapper messageMediaMapper;

    @Autowired
    private SessionArchivePullService sessionArchivePullService;

    @Autowired
    private FinanceSdkClient financeSdkClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void shouldReceiveMsgAuditNotifyAndPublishPullCommand() throws Exception {
        ChannelSubject subject = createSessionArchiveSubject();

        String xml = """
                <xml>
                    <ToUserName><![CDATA[%s]]></ToUserName>
                    <FromUserName><![CDATA[sys]]></FromUserName>
                    <CreateTime>1700000000</CreateTime>
                    <MsgType><![CDATA[event]]></MsgType>
                    <Event><![CDATA[msgaudit_notify]]></Event>
                </xml>
                """.formatted(CORP_ID);

        mockMvc.perform(post("/msg-platform/callback/wecom")
                        .contextPath("/msg-platform")
                        .contentType(MediaType.APPLICATION_XML)
                        .content(xml))
                .andExpect(status().isOk())
                .andExpect(content().string("success"));

        ArgumentCaptor<SessionArchivePullCommand> captor = ArgumentCaptor.forClass(SessionArchivePullCommand.class);
        verify(rabbitTemplate).convertAndSend(eq(RabbitConfig.SESSION_ARCHIVE_EXCHANGE),
                eq(RabbitConfig.SESSION_ARCHIVE_ROUTING_KEY), captor.capture());
        SessionArchivePullCommand command = captor.getValue();
        assertThat(command.getChannelSubjectId()).isEqualTo(subject.getId());
        assertThat(command.getCorpId()).isEqualTo(CORP_ID);
        assertThat(command.getTenantCode()).isEqualTo(subject.getTenantCode());
    }

    @Test
    void shouldPullAndSaveMessageEvent() throws Exception {
        ChannelSubject subject = createSessionArchiveSubject();
        when(financeSdkClient.init(anyString(), anyString())).thenReturn(true);
        when(financeSdkClient.getChatData(anyLong(), anyInt())).thenReturn(List.of(
                FinanceMessage.builder()
                        .seq(100L)
                        .msgId("msg-100")
                        .publicKeyVer(1)
                        .encryptRandomKey("random-100")
                        .encryptChatMsg("chat-100")
                        .build()
        ));
        when(financeSdkClient.decryptData(anyString(), anyString())).thenReturn(
                objectMapper.writeValueAsString(Map.of(
                        "msgid", "msg-100",
                        "msgtype", "text",
                        "text", Map.of("content", "hello")
                ))
        );

        SessionArchivePullCommand command = new SessionArchivePullCommand();
        command.setChannelSubjectId(subject.getId());
        command.setTenantCode(subject.getTenantCode());
        command.setCorpId(subject.getCorpId());
        command.setSecret(subject.getAgentSecret());
        command.setPrivateKey(subject.getSessionArchivePrivateKey());

        sessionArchivePullService.onPullCommand(command);

        MessageEvent event = messageEventMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MessageEvent>()
                        .eq(MessageEvent::getEventId, "msg-100"));
        assertThat(event).isNotNull();
        assertThat(event.getMessageSource()).isEqualTo("session_archive");
        assertThat(event.getChannelSubjectId()).isEqualTo(subject.getId());
        assertThat(event.getChannelType()).isEqualTo("wecom");
        assertThat(event.getContent()).contains("hello");
    }

    @Test
    void shouldSaveMediaRecordForImageMessage() throws Exception {
        ChannelSubject subject = createSessionArchiveSubject();
        when(financeSdkClient.init(anyString(), anyString())).thenReturn(true);
        when(financeSdkClient.getChatData(anyLong(), anyInt())).thenReturn(List.of(
                FinanceMessage.builder()
                        .seq(200L)
                        .msgId("msg-200")
                        .publicKeyVer(1)
                        .encryptRandomKey("random-200")
                        .encryptChatMsg("chat-200")
                        .build()
        ));
        when(financeSdkClient.decryptData(anyString(), anyString())).thenReturn(
                objectMapper.writeValueAsString(Map.of(
                        "msgid", "msg-200",
                        "msgtype", "image",
                        "image", Map.of(
                                "sdkfileid", "sdkfileid-200",
                                "filesize", 1024,
                                "md5sum", "abc123"
                        )
                ))
        );

        SessionArchivePullCommand command = new SessionArchivePullCommand();
        command.setChannelSubjectId(subject.getId());
        command.setTenantCode(subject.getTenantCode());
        command.setCorpId(subject.getCorpId());
        command.setSecret(subject.getAgentSecret());
        command.setPrivateKey(subject.getSessionArchivePrivateKey());

        sessionArchivePullService.onPullCommand(command);

        MessageMedia media = messageMediaMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MessageMedia>()
                        .eq(MessageMedia::getMsgid, "msg-200"));
        assertThat(media).isNotNull();
        assertThat(media.getSdkfileid()).isEqualTo("sdkfileid-200");
        assertThat(media.getMediaType()).isEqualTo("image");
        assertThat(media.getStatus()).isEqualTo(0);
        assertThat(media.getFileSize()).isEqualTo(1024L);
    }

    private ChannelSubject createSessionArchiveSubject() throws Exception {
        String tenantCode = "sa-" + UUID.randomUUID().toString().substring(0, 8);
        createTenantAndLogin(mockMvc, tenantCode, "会话存档租户");

        ChannelSubject subject = new ChannelSubject();
        subject.setTenantCode(tenantCode);
        subject.setChannelType("wecom");
        subject.setSubjectCode("archive");
        subject.setSubjectName("会话存档主体");
        subject.setCorpId(CORP_ID);
        subject.setAgentSecret("secret-" + UUID.randomUUID());
        subject.setSessionArchiveEnabled(1);
        subject.setSessionArchivePrivateKey("private-key");
        subject.setStatus(1);
        channelSubjectMapper.insert(subject);
        return subject;
    }

    @TestConfiguration
    static class MockConfig {

        @Bean
        public FinanceSdkClient financeSdkClient() {
            return mock(FinanceSdkClient.class);
        }

        @Bean
        public RabbitTemplate rabbitTemplate() {
            return mock(RabbitTemplate.class);
        }
    }
}
