package com.sino.mci.send.service;

import com.sino.mci.channel.adapter.registry.ChannelAdapterRegistry;
import com.sino.mci.channel.common.model.ChannelType;
import com.sino.mci.channel.entity.ChannelAccount;
import com.sino.mci.channel.entity.Conversation;
import com.sino.mci.channel.repository.ChannelAccountConversationMapper;
import com.sino.mci.channel.repository.ChannelAccountMapper;
import com.sino.mci.channel.repository.ConversationMapper;
import com.sino.mci.channel.repository.ConversationTagBindingMapper;
import com.sino.mci.channel.service.AccountSelector;
import com.sino.mci.crm.repository.ChannelIdentityMapper;
import com.sino.mci.crm.entity.ChannelIdentity;
import com.sino.mci.crm.repository.PersonMapper;
import com.sino.mci.crm.repository.PersonTagBindingMapper;
import com.sino.mci.crm.repository.TagMapper;
import com.sino.mci.exception.BusinessException;
import com.sino.mci.policy.engine.PolicyEngine;
import com.sino.mci.policy.repository.PolicyGroupMapper;
import com.sino.mci.send.dto.SendTestRequest;
import com.sino.mci.send.mq.SendMessageProducer;
import com.sino.mci.send.repository.SendRecordMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class SendServiceResolveTargetTest {

    @Mock
    private SendRecordMapper sendRecordMapper;
    @Mock
    private ConversationMapper conversationMapper;
    @Mock
    private ChannelAccountMapper accountMapper;
    @Mock
    private ChannelAccountConversationMapper channelAccountConversationMapper;
    @Mock
    private ChannelIdentityMapper channelIdentityMapper;
    @Mock
    private PersonMapper personMapper;
    @Mock
    private TagMapper tagMapper;
    @Mock
    private PersonTagBindingMapper personTagBindingMapper;
    @Mock
    private ConversationTagBindingMapper conversationTagBindingMapper;
    @Mock
    private ChannelAdapterRegistry adapterRegistry;
    @Mock
    private PolicyGroupMapper policyGroupMapper;
    @Mock
    private PolicyEngine policyEngine;
    @Mock
    private AccountSelector accountSelector;
    @Mock
    private SendMessageProducer sendMessageProducer;

    @InjectMocks
    private SendService sendService;

    @Test
    void resolveConversationTarget_selectsHealthyPrimaryAccount() {
        Conversation conversation = createConversation();
        Long primaryAccountId = 10L;

        when(conversationMapper.selectById(1L)).thenReturn(conversation);
        when(accountSelector.selectSendAccounts("tenant", 1L, ChannelType.WECOM, "primary"))
                .thenReturn(Collections.singletonList(primaryAccountId));
        when(accountMapper.selectById(primaryAccountId)).thenReturn(createAccount(primaryAccountId, "wecom", 1));

        List<SendTarget> targets = sendService.resolveTestTargets("tenant", createConversationRequest("primary"));

        assertEquals(1, targets.size());
        assertEquals(primaryAccountId, targets.get(0).getAccountId());
        assertEquals(ChannelType.WECOM, targets.get(0).getChannelType());
    }

    @Test
    void resolveConversationTarget_rejectsUnhealthyOwnerAccount() {
        Conversation conversation = createConversation();
        conversation.setOwnerAccountId(20L);

        when(conversationMapper.selectById(1L)).thenReturn(conversation);
        when(accountSelector.selectSendAccounts("tenant", 1L, ChannelType.WECOM, "primary"))
                .thenReturn(Collections.emptyList());
        ChannelAccount disabledAccount = createAccount(20L, "wecom", 0);
        when(accountMapper.selectById(20L)).thenReturn(disabledAccount);

        BusinessException ex = assertThrows(BusinessException.class,
                () -> sendService.resolveTestTargets("tenant", createConversationRequest("primary")));
        assertEquals("会话没有可用的发送账号: 1", ex.getMessage());
    }

    @Test
    void resolveConversationTarget_usesAccountReturnedBySelector() {
        Conversation conversation = createConversation();
        Long backupAccountId = 20L;

        when(conversationMapper.selectById(1L)).thenReturn(conversation);
        when(accountSelector.selectSendAccounts("tenant", 1L, ChannelType.WECOM, "primary"))
                .thenReturn(Collections.singletonList(backupAccountId));
        when(accountMapper.selectById(backupAccountId)).thenReturn(createAccount(backupAccountId, "wecom", 1));

        List<SendTarget> targets = sendService.resolveTestTargets("tenant", createConversationRequest("primary"));

        assertEquals(1, targets.size());
        assertEquals(backupAccountId, targets.get(0).getAccountId());
    }

    @Test
    void resolveConversationTarget_respectsBackupStrategy() {
        Conversation conversation = createConversation();
        Long backupAccountId = 20L;

        when(conversationMapper.selectById(1L)).thenReturn(conversation);
        when(accountSelector.selectSendAccounts("tenant", 1L, ChannelType.WECOM, "backup"))
                .thenReturn(Collections.singletonList(backupAccountId));
        when(accountMapper.selectById(backupAccountId)).thenReturn(createAccount(backupAccountId, "wecom", 1));

        List<SendTarget> targets = sendService.resolveTestTargets("tenant", createConversationRequest("backup"));

        assertEquals(1, targets.size());
        assertEquals(backupAccountId, targets.get(0).getAccountId());

        ArgumentCaptor<String> strategyCaptor = ArgumentCaptor.forClass(String.class);
        verify(accountSelector).selectSendAccounts(eq("tenant"), eq(1L), eq(ChannelType.WECOM), strategyCaptor.capture());
        assertEquals("backup", strategyCaptor.getValue());
    }

    @Test
    void resolvePersonTarget_rejectsDisabledAccount() {
        ChannelIdentity identity = createIdentity();
        ChannelAccount account = createAccount(30L, "wecom", 0);

        when(channelIdentityMapper.selectOne(any())).thenReturn(identity);
        when(accountMapper.selectList(any())).thenReturn(Collections.singletonList(account));
        when(accountMapper.selectById(30L)).thenReturn(account);

        BusinessException ex = assertThrows(BusinessException.class,
                () -> sendService.resolveTestTargets("tenant", createPersonRequest()));
        assertEquals("租户没有可用的发送账号: channel=wecom", ex.getMessage());
    }

    @Test
    void resolvePersonTarget_rejectsCrossTenantAccount() {
        ChannelIdentity identity = createIdentity();
        ChannelAccount account = createAccount(30L, "wecom", 1);
        account.setTenantCode("other");

        when(channelIdentityMapper.selectOne(any())).thenReturn(identity);
        when(accountMapper.selectList(any())).thenReturn(Collections.singletonList(account));
        when(accountMapper.selectById(30L)).thenReturn(account);

        BusinessException ex = assertThrows(BusinessException.class,
                () -> sendService.resolveTestTargets("tenant", createPersonRequest()));
        assertEquals("租户没有可用的发送账号: channel=wecom", ex.getMessage());
    }

    @Test
    void resolvePersonTarget_rejectsChannelTypeMismatch() {
        ChannelIdentity identity = createIdentity();
        ChannelAccount account = createAccount(30L, "mobile", 1);

        when(channelIdentityMapper.selectOne(any())).thenReturn(identity);
        when(accountMapper.selectList(any())).thenReturn(Collections.singletonList(account));
        when(accountMapper.selectById(30L)).thenReturn(account);

        BusinessException ex = assertThrows(BusinessException.class,
                () -> sendService.resolveTestTargets("tenant", createPersonRequest()));
        assertEquals("租户没有可用的发送账号: channel=wecom", ex.getMessage());
    }

    private SendTestRequest createConversationRequest(String strategy) {
        SendTestRequest request = new SendTestRequest();
        request.setTargetType("conversation");
        Map<String, Object> targetValue = new HashMap<>();
        targetValue.put("conversation_id", 1L);
        request.setTargetValue(targetValue);
        request.setContentType("text");
        request.setContent(Collections.singletonMap("text", "hello"));

        Map<String, Object> channelStrategy = new HashMap<>();
        channelStrategy.put("channel_type", "wecom");
        channelStrategy.put("strategy", strategy);
        request.setChannelStrategy(channelStrategy);
        return request;
    }

    private SendTestRequest createPersonRequest() {
        SendTestRequest request = new SendTestRequest();
        request.setTargetType("person");
        Map<String, Object> targetValue = new HashMap<>();
        targetValue.put("person_id", 1L);
        request.setTargetValue(targetValue);
        request.setContentType("text");
        request.setContent(Collections.singletonMap("text", "hello"));

        Map<String, Object> channelStrategy = new HashMap<>();
        channelStrategy.put("channel_type", "wecom");
        request.setChannelStrategy(channelStrategy);
        return request;
    }

    private Conversation createConversation() {
        Conversation conversation = new Conversation();
        conversation.setId(1L);
        conversation.setTenantCode("tenant");
        conversation.setChannelType("wecom");
        conversation.setChannelConversationId("conv-1");
        conversation.setStatus(1);
        return conversation;
    }

    private ChannelAccount createAccount(Long id, String channelType, int status) {
        ChannelAccount account = new ChannelAccount();
        account.setId(id);
        account.setTenantCode("tenant");
        account.setChannelType(channelType);
        account.setStatus(status);
        return account;
    }

    private ChannelIdentity createIdentity() {
        ChannelIdentity identity = new ChannelIdentity();
        identity.setTenantCode("tenant");
        identity.setPersonId(1L);
        identity.setChannelType("wecom");
        identity.setChannelUserId("user-1");
        identity.setStatus(1);
        return identity;
    }
}
