package com.sino.mci.send.service;

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.channel.entity.ChannelAccount;
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.repository.PersonMapper;
import com.sino.mci.crm.repository.PersonTagBindingMapper;
import com.sino.mci.crm.repository.TagMapper;
import com.sino.mci.policy.engine.PolicyEngine;
import com.sino.mci.policy.repository.PolicyGroupMapper;
import com.sino.mci.send.entity.SendRecord;
import com.sino.mci.send.repository.SendRecordMapper;
import com.sino.mci.shared.util.JsonUtils;
import org.junit.jupiter.api.BeforeEach;
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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class SendServiceFailoverTest {

    @Mock
    private SendRecordMapper sendRecordMapper;
    @Mock
    private ConversationMapper conversationMapper;
    @Mock
    private ChannelAccountMapper accountMapper;
    @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 ChannelAdapter adapter;

    @InjectMocks
    private SendService sendService;

    @BeforeEach
    void setUp() {
        when(adapterRegistry.getAdapter(ChannelType.WECHAT_PERSONAL)).thenReturn(adapter);
    }

    @Test
    void dispatchRecordSucceedsAfterFailoverToBackup() {
        SendRecord record = createRecord();
        Long primaryAccountId = 100L;
        Long backupAccountId = 200L;

        when(accountSelector.selectSendAccounts("tenant", 1L, ChannelType.WECHAT_PERSONAL, "primary"))
                .thenReturn(Arrays.asList(primaryAccountId, backupAccountId));
        when(accountMapper.selectById(primaryAccountId)).thenReturn(createPywechatAccount(primaryAccountId, "wxid-primary"));
        when(accountMapper.selectById(backupAccountId)).thenReturn(createPywechatAccount(backupAccountId, "wxid-backup"));

        SendMessageResult failureResult = new SendMessageResult();
        failureResult.setSuccess(false);
        failureResult.setMessage("login expired");

        SendMessageResult successResult = new SendMessageResult();
        successResult.setSuccess(true);
        successResult.setChannelMessageId("msg-backup-001");

        when(adapter.sendMessage(any(SendMessageRequest.class))).thenReturn(failureResult, successResult);

        sendService.dispatchRecord(record);

        assertEquals(2, record.getSendStatus().intValue());
        assertEquals(backupAccountId, record.getAccountId());

        ArgumentCaptor<SendMessageRequest> requestCaptor = ArgumentCaptor.forClass(SendMessageRequest.class);
        verify(adapter, times(2)).sendMessage(requestCaptor.capture());
        List<SendMessageRequest> requests = requestCaptor.getAllValues();
        assertEquals("wxid-primary", requests.get(0).getExtra().get("account_id"));
        assertEquals("wxid-backup", requests.get(1).getExtra().get("account_id"));

        ArgumentCaptor<ChannelAccount> accountCaptor = ArgumentCaptor.forClass(ChannelAccount.class);
        verify(accountMapper, times(1)).updateById(accountCaptor.capture());
        ChannelAccount markedAccount = accountCaptor.getValue();
        assertEquals(primaryAccountId, markedAccount.getId());
        assertEquals(2, markedAccount.getRiskLevel().intValue());
        assertEquals(0, markedAccount.getOnlineStatus().intValue());
    }

    @Test
    void dispatchRecordFailsWhenAllAccountsUnhealthy() {
        SendRecord record = createRecord();
        Long accountId = 100L;

        when(accountSelector.selectSendAccounts("tenant", 1L, ChannelType.WECHAT_PERSONAL, "primary"))
                .thenReturn(Collections.singletonList(accountId));
        when(accountMapper.selectById(accountId)).thenReturn(createPywechatAccount(accountId, "wxid-primary"));

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

        sendService.dispatchRecord(record);

        assertEquals(3, record.getSendStatus().intValue());
        assertEquals(accountId, record.getAccountId());

        verify(adapter, times(1)).sendMessage(any(SendMessageRequest.class));

        ArgumentCaptor<ChannelAccount> accountCaptor = ArgumentCaptor.forClass(ChannelAccount.class);
        verify(accountMapper, times(1)).updateById(accountCaptor.capture());
        ChannelAccount markedAccount = accountCaptor.getValue();
        assertEquals(2, markedAccount.getRiskLevel().intValue());
        assertEquals(0, markedAccount.getOnlineStatus().intValue());

        Map<String, Object> result = JsonUtils.fromJson(record.getChannelResult(), Map.class);
        assertNotNull(result);
        assertEquals("all_accounts_failed", result.get("error"));
        List<?> attempts = (List<?>) result.get("attempts");
        assertFalse(attempts.isEmpty());
        assertEquals(accountId, ((Number) ((Map<?, ?>) attempts.get(0)).get("account_id")).longValue());
    }

    @Test
    void dispatchRecordFailsWithNoHealthyAccount() {
        SendRecord record = createRecord();

        when(accountSelector.selectSendAccounts("tenant", 1L, ChannelType.WECHAT_PERSONAL, "primary"))
                .thenReturn(Collections.emptyList());

        sendService.dispatchRecord(record);

        assertEquals(3, record.getSendStatus().intValue());
        verify(adapter, never()).sendMessage(any(SendMessageRequest.class));
        verify(accountMapper, never()).updateById(any(ChannelAccount.class));

        Map<String, Object> result = JsonUtils.fromJson(record.getChannelResult(), Map.class);
        assertEquals("no_healthy_account", result.get("error"));
    }

    private SendRecord createRecord() {
        SendRecord record = new SendRecord();
        record.setId(1L);
        record.setTaskId("SND001");
        record.setTenantCode("tenant");
        record.setConversationId(1L);
        record.setAccountId(100L);
        record.setChannelType("wechat_personal");
        record.setChannelStrategy("{\"channel_type\":\"wechat_personal\",\"strategy\":\"primary\"}");
        record.setContentType("text");
        record.setContentBody("{\"text\":\"hello\"}");
        record.setSendStatus(0);
        return record;
    }

    private ChannelAccount createPywechatAccount(Long id, String accountId) {
        ChannelAccount account = new ChannelAccount();
        account.setId(id);
        account.setTenantCode("tenant");
        account.setAccountType(3);
        account.setChannelType("wechat_personal");
        account.setAccountId(accountId);
        account.setStatus(1);
        account.setOnlineStatus(1);
        account.setRiskLevel(1);
        return account;
    }
}
