package com.sino.mci.channel.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sino.mci.channel.common.model.ChannelType;
import com.sino.mci.channel.entity.ChannelAccount;
import com.sino.mci.channel.entity.ChannelAccountConversation;
import com.sino.mci.channel.entity.ChannelSubject;
import com.sino.mci.channel.entity.Conversation;
import com.sino.mci.channel.entity.ConversationParticipant;
import com.sino.mci.channel.entity.SyncTask;
import com.sino.mci.channel.repository.ChannelAccountConversationMapper;
import com.sino.mci.channel.repository.ChannelAccountMapper;
import com.sino.mci.channel.repository.ChannelSubjectMapper;
import com.sino.mci.channel.repository.ConversationMapper;
import com.sino.mci.channel.repository.ConversationParticipantMapper;
import com.sino.mci.channel.service.SyncTaskService;
import com.sino.mci.channel.wecom.sync.WeComContact;
import com.sino.mci.channel.wecom.sync.WeComContactType;
import com.sino.mci.channel.wecom.sync.WeComConversation;
import com.sino.mci.channel.wecom.sync.WeComConversationMember;
import com.sino.mci.channel.wecom.sync.WeComSyncClient;
import com.sino.mci.crm.entity.ChannelIdentity;
import com.sino.mci.crm.entity.Person;
import com.sino.mci.crm.repository.ChannelIdentityMapper;
import com.sino.mci.crm.repository.PersonMapper;
import org.junit.jupiter.api.Test;
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.test.web.servlet.MvcResult;

import java.util.List;
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.Mockito.mock;
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.jsonPath;
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",
        "mci.wecom.sync.mock=true"
})
class WeComSyncTest {

    private final String tenantCode = "wecom-sync-" + UUID.randomUUID().toString().substring(0, 8);
    private static final String CHANNEL_TYPE = ChannelType.WECOM.name().toLowerCase();

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ChannelSubjectMapper subjectMapper;

    @Autowired
    private ChannelAccountMapper accountMapper;

    @Autowired
    private PersonMapper personMapper;

    @Autowired
    private ChannelIdentityMapper channelIdentityMapper;

    @Autowired
    private ConversationMapper conversationMapper;

    @Autowired
    private ConversationParticipantMapper conversationParticipantMapper;

    @Autowired
    private ChannelAccountConversationMapper accountConversationMapper;

    @Autowired
    private SyncTaskService syncTaskService;

    @Test
    void shouldSyncEnterpriseContactsViaSubjectAndPersistInternalPerson() throws Exception {
        String authToken = createTenantAndLogin(mockMvc, tenantCode, "企微同步租户");
        ChannelSubject subject = createWeComSubject();

        MvcResult result = mockMvc.perform(post("/msg-platform/api/v1/channel-subject/{id}/sync-contacts", subject.getId())
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andReturn();

        Long taskId = extractTaskId(result);
        SyncTask task = waitForTask(taskId);
        assertThat(task.getTaskStatus()).isEqualTo(SyncTask.STATUS_SUCCESS);
        assertThat(task.getSyncedCount()).isEqualTo(2);

        Person internalPerson = personMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Person>()
                        .eq(Person::getTenantCode, tenantCode)
                        .eq(Person::getPersonCode, "user001"));
        assertThat(internalPerson).isNotNull();
        assertThat(internalPerson.getPersonName()).isEqualTo("张三");
        assertThat(internalPerson.getPhone()).isEqualTo("13800138001");
        assertThat(internalPerson.getPersonType()).isEqualTo(1);

        ChannelIdentity internalIdentity = channelIdentityMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ChannelIdentity>()
                        .eq(ChannelIdentity::getTenantCode, tenantCode)
                        .eq(ChannelIdentity::getChannelType, CHANNEL_TYPE)
                        .eq(ChannelIdentity::getChannelUserId, "user001"));
        assertThat(internalIdentity).isNotNull();
        assertThat(internalIdentity.getPersonId()).isEqualTo(internalPerson.getId());
        assertThat(internalIdentity.getChannelUserName()).isEqualTo("张三");
    }

    @Test
    void shouldSyncAccountContactsAndPersistExternalPerson() throws Exception {
        String authToken = createTenantAndLogin(mockMvc, tenantCode, "企微同步租户");
        ChannelAccount account = createWeComAccount();

        MvcResult result = mockMvc.perform(post("/msg-platform/api/v1/channel-account/{account_id}/sync-contacts", account.getId())
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andReturn();

        Long taskId = extractTaskId(result);
        SyncTask task = waitForTask(taskId);
        assertThat(task.getTaskStatus()).isEqualTo(SyncTask.STATUS_SUCCESS);

        Person externalPerson = personMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Person>()
                        .eq(Person::getTenantCode, tenantCode)
                        .eq(Person::getPersonCode, "wmexternal001"));
        assertThat(externalPerson).isNotNull();
        assertThat(externalPerson.getPersonType()).isEqualTo(3);

        ChannelIdentity externalIdentity = channelIdentityMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ChannelIdentity>()
                        .eq(ChannelIdentity::getTenantCode, tenantCode)
                        .eq(ChannelIdentity::getChannelType, CHANNEL_TYPE)
                        .eq(ChannelIdentity::getChannelUserId, "wmexternal001"));
        assertThat(externalIdentity).isNotNull();
        assertThat(externalIdentity.getPersonId()).isEqualTo(externalPerson.getId());
    }

    @Test
    void shouldSyncConversationsAndPersistParticipantsAndBindings() throws Exception {
        String authToken = createTenantAndLogin(mockMvc, tenantCode, "企微同步租户");
        ChannelAccount account = createWeComAccount();

        MvcResult result = mockMvc.perform(post("/msg-platform/api/v1/channel-account/{account_id}/sync-conversations", account.getId())
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andReturn();

        Long taskId = extractTaskId(result);
        SyncTask task = waitForTask(taskId);
        assertThat(task.getTaskStatus()).isEqualTo(SyncTask.STATUS_SUCCESS);

        Conversation conversation = conversationMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Conversation>()
                        .eq(Conversation::getTenantCode, tenantCode)
                        .eq(Conversation::getChannelType, CHANNEL_TYPE)
                        .eq(Conversation::getChannelConversationId, "chat001"));
        assertThat(conversation).isNotNull();
        assertThat(conversation.getConversationType()).isEqualTo(2);
        assertThat(conversation.getConversationName()).isEqualTo("测试客户群");
        assertThat(conversation.getOwnerAccountId()).isEqualTo(account.getId());

        List<ConversationParticipant> participants = conversationParticipantMapper.selectList(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ConversationParticipant>()
                        .eq(ConversationParticipant::getConversationId, conversation.getId()));
        assertThat(participants).hasSize(2);

        ConversationParticipant ownerParticipant = participants.stream()
                .filter(p -> p.getParticipantType() == 2)
                .findFirst()
                .orElseThrow();
        Person ownerPerson = personMapper.selectById(ownerParticipant.getPersonId());
        assertThat(ownerPerson.getPersonCode()).isEqualTo("user001");

        ChannelAccountConversation sendMaster = accountConversationMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ChannelAccountConversation>()
                        .eq(ChannelAccountConversation::getAccountId, account.getId())
                        .eq(ChannelAccountConversation::getConversationId, conversation.getId())
                        .eq(ChannelAccountConversation::getBindingType, 1));
        assertThat(sendMaster).isNotNull();
        assertThat(sendMaster.getStatus()).isEqualTo(1);

        ChannelAccountConversation receiveMaster = accountConversationMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ChannelAccountConversation>()
                        .eq(ChannelAccountConversation::getAccountId, account.getId())
                        .eq(ChannelAccountConversation::getConversationId, conversation.getId())
                        .eq(ChannelAccountConversation::getBindingType, 3));
        assertThat(receiveMaster).isNotNull();
    }

    @Test
    void shouldPreservePersonBusinessFieldsDuringContactSync() throws Exception {
        String authToken = createTenantAndLogin(mockMvc, tenantCode, "企微同步租户");
        ChannelAccount account = createWeComAccount();

        Person person = new Person();
        person.setTenantCode(tenantCode);
        person.setPersonCode("wmexternal001");
        person.setPersonName("原始姓名");
        person.setPhone("13900139001");
        person.setPersonType(1);
        person.setInstitutionId(100L);
        person.setSupplierId(200L);
        person.setStatus(1);
        personMapper.insert(person);

        ChannelIdentity identity = new ChannelIdentity();
        identity.setTenantCode(tenantCode);
        identity.setPersonId(person.getId());
        identity.setChannelType(CHANNEL_TYPE);
        identity.setChannelUserId("wmexternal001");
        identity.setChannelUserName("原始姓名");
        identity.setSubjectId(account.getSubjectId());
        identity.setStatus(1);
        channelIdentityMapper.insert(identity);

        MvcResult result = mockMvc.perform(post("/msg-platform/api/v1/channel-account/{account_id}/sync-contacts", account.getId())
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andReturn();

        Long taskId = extractTaskId(result);
        SyncTask task = waitForTask(taskId);
        assertThat(task.getTaskStatus()).isEqualTo(SyncTask.STATUS_SUCCESS);

        Person updated = personMapper.selectById(person.getId());
        assertThat(updated.getPersonName()).isEqualTo("客户李四");
        assertThat(updated.getInstitutionId()).isEqualTo(100L);
        assertThat(updated.getSupplierId()).isEqualTo(200L);
        assertThat(updated.getPersonType()).isEqualTo(1);
    }

    @Test
    void shouldPreserveConversationBusinessFieldsDuringConversationSync() throws Exception {
        String authToken = createTenantAndLogin(mockMvc, tenantCode, "企微同步租户");
        ChannelAccount account = createWeComAccount();

        Conversation conversation = new Conversation();
        conversation.setTenantCode(tenantCode);
        conversation.setConversationType(2);
        conversation.setChannelType(CHANNEL_TYPE);
        conversation.setChannelConversationId("chat001");
        conversation.setOwnerAccountId(account.getId());
        conversation.setSubjectId(account.getSubjectId());
        conversation.setConversationName("原始群名");
        conversation.setInstitutionId(100L);
        conversation.setSupplierId(200L);
        conversation.setContractId(300L);
        conversation.setChannelCode("ORIGINAL");
        conversation.setCreateSource(1);
        conversation.setStatus(1);
        conversationMapper.insert(conversation);

        MvcResult result = mockMvc.perform(post("/msg-platform/api/v1/channel-account/{account_id}/sync-conversations", account.getId())
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andReturn();

        Long taskId = extractTaskId(result);
        SyncTask task = waitForTask(taskId);
        assertThat(task.getTaskStatus()).isEqualTo(SyncTask.STATUS_SUCCESS);

        Conversation updated = conversationMapper.selectById(conversation.getId());
        assertThat(updated.getConversationName()).isEqualTo("测试客户群");
        assertThat(updated.getInstitutionId()).isEqualTo(100L);
        assertThat(updated.getSupplierId()).isEqualTo(200L);
        assertThat(updated.getContractId()).isEqualTo(300L);
        assertThat(updated.getChannelCode()).isEqualTo("ORIGINAL");
    }

    private Long extractTaskId(MvcResult result) throws Exception {
        return objectMapper.readTree(result.getResponse().getContentAsString()).path("data").path("id").asLong();
    }

    private SyncTask waitForTask(Long taskId) throws InterruptedException {
        for (int i = 0; i < 50; i++) {
            SyncTask task = syncTaskService.getTaskById(taskId);
            if (task != null && (task.getTaskStatus() == SyncTask.STATUS_SUCCESS || task.getTaskStatus() == SyncTask.STATUS_FAILED)) {
                return task;
            }
            Thread.sleep(200);
        }
        throw new RuntimeException("同步任务未在预期时间内完成: taskId=" + taskId);
    }

    private ChannelSubject createWeComSubject() {
        ChannelSubject subject = new ChannelSubject();
        subject.setTenantCode(tenantCode);
        subject.setChannelType(CHANNEL_TYPE);
        subject.setSubjectCode("sino");
        subject.setSubjectName("Sino");
        subject.setCorpId("ww-sync-" + UUID.randomUUID().toString().substring(0, 8));
        subject.setAgentId("1000002");
        subject.setAgentSecret("secret-" + UUID.randomUUID());
        subject.setStatus(1);
        subjectMapper.insert(subject);
        return subject;
    }

    private ChannelAccount createWeComAccount() {
        ChannelSubject subject = createWeComSubject();

        ChannelAccount account = new ChannelAccount();
        account.setTenantCode(tenantCode);
        account.setAccountType(1);
        account.setChannelType(CHANNEL_TYPE);
        account.setSubjectId(subject.getId());
        account.setAccountId("sync-account");
        account.setAccountName("同步账号");
        account.setStatus(1);
        accountMapper.insert(account);
        return account;
    }

    @TestConfiguration
    static class MockConfig {

        @Bean
        public WeComSyncClient weComSyncClient() {
            WeComSyncClient client = mock(WeComSyncClient.class);
            when(client.syncEnterpriseContacts(any())).thenReturn(List.of(
                    WeComContact.builder()
                            .userId("user001")
                            .name("张三")
                            .mobile("13800138001")
                            .type(WeComContactType.INTERNAL)
                            .build(),
                    WeComContact.builder()
                            .userId("wmexternal001")
                            .name("客户李四")
                            .mobile("13900139001")
                            .type(WeComContactType.EXTERNAL)
                            .build()
            ));
            when(client.syncAccountContacts(any(), any())).thenReturn(List.of(
                    WeComContact.builder()
                            .userId("wmexternal001")
                            .name("客户李四")
                            .mobile("13900139001")
                            .type(WeComContactType.EXTERNAL)
                            .build()
            ));
            when(client.syncAccountConversations(any(), any())).thenReturn(List.of(
                    WeComConversation.builder()
                            .chatId("chat001")
                            .chatName("测试客户群")
                            .ownerUserId("user001")
                            .memberList(List.of(
                                    WeComConversationMember.builder().userId("user001").name("张三").build(),
                                    WeComConversationMember.builder().userId("wmexternal001").name("客户李四").build()
                            ))
                            .build()
            ));
            return client;
        }
    }
}
