package com.ruoyi.web.modules.diagnosis.service.impl;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.modules.diagnosis.AiOnlineConsultTestSamples;
import com.ruoyi.web.modules.diagnosis.domain.AiConsultOpenBody;
import com.ruoyi.web.modules.diagnosis.domain.AiConsultSendResult;
import com.ruoyi.web.modules.diagnosis.domain.BizConsultSession;
import com.ruoyi.web.modules.diagnosis.domain.VetConsultSendBody;
import com.ruoyi.web.modules.diagnosis.domain.BizConsultUser;
import com.ruoyi.web.modules.diagnosis.mapper.BizConsultUserMapper;
import com.ruoyi.web.modules.diagnosis.mapper.BizConsultMessageMapper;
import com.ruoyi.web.modules.diagnosis.mapper.BizConsultSessionMapper;
import com.ruoyi.web.modules.diagnosis.support.AiConsultConstants;
import com.ruoyi.web.modules.diagnosis.support.ConsultAiGateway;
import com.ruoyi.web.modules.diagnosis.support.ConsultPushService;
import com.ruoyi.web.modules.diagnosis.support.ConsultSessionProcessingLock;
import com.ruoyi.web.modules.diagnosis.support.ConsultSessionRules;

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("BizAiOnlineConsultServiceImpl")
class BizAiOnlineConsultServiceImplTest
{
    @Mock
    private BizConsultSessionMapper sessionMapper;

    @Mock
    private BizConsultUserMapper consultUserMapper;

    @Mock
    private BizConsultMessageMapper messageMapper;

    @Mock
    private ConsultAiGateway consultAiGateway;

    @Mock
    private ConsultPushService consultPushService;

    @Mock
    private ConsultSessionProcessingLock processingLock;

    @InjectMocks
    private BizAiOnlineConsultServiceImpl service;

    @BeforeEach
    void defaults()
    {
        when(processingLock.tryLock(anyLong())).thenReturn(true);
        when(consultAiGateway.generateReply(anyInt(), any(), any())).thenReturn("AI参考建议");
    }

    @Test
    @DisplayName("列表按提问人查询")
    void listQueriesSessions()
    {
        when(sessionMapper.selectAiSessionList(any())).thenReturn(Collections.emptyList());
        assertTrue(service.selectAiSessionList(new BizConsultSession(), 99L).isEmpty());
        verify(sessionMapper).selectAiSessionList(any());
    }

    @Test
    @DisplayName("默认列表含3个月条件")
    void listDefaultWindow()
    {
        when(sessionMapper.selectAiSessionList(any()))
                .thenReturn(Collections.singletonList(AiOnlineConsultTestSamples.activeAiSession()));
        service.selectAiSessionList(new BizConsultSession(), AiOnlineConsultTestSamples.VA_USER_ID);
        verify(sessionMapper).selectAiSessionList(argThat(c -> c.getThreeMonthsAgo() != null
                && c.getConsultType() == ConsultSessionRules.CONSULT_TYPE_AI));
    }

    @Test
    @DisplayName("创建会话 receiver 全 NULL")
    void createSession()
    {
        when(consultUserMapper.selectByUserId(AiOnlineConsultTestSamples.VA_USER_ID)).thenReturn(null);
        service.createAiSession(AiOnlineConsultTestSamples.VA_USER_ID, "甲", null);
        verify(sessionMapper).insertBizConsultSession(argThat(r -> r.getConsultType() == ConsultSessionRules.CONSULT_TYPE_AI
                && r.getReceiverProviderId() == null
                && r.getReceiverUserId() == null
                && AiConsultConstants.SESSION_TITLE_NEW.equals(r.getSessionTitle())
                && r.getLastMessageTime() != null));
        verify(consultUserMapper).insertBizConsultUser(argThat(u -> u != null
                && AiOnlineConsultTestSamples.VA_USER_ID.equals(u.getUserId())
                && u.getFirstUseTime() != null
                && u.getLastUseTime() != null));
    }

    @Test
    @DisplayName("创建会话不重复插入用户")
    void createSessionSkipsExistingUser()
    {
        BizConsultUser existing = new BizConsultUser();
        existing.setUserId(AiOnlineConsultTestSamples.VA_USER_ID);
        when(consultUserMapper.selectByUserId(AiOnlineConsultTestSamples.VA_USER_ID)).thenReturn(existing);
        service.createAiSession(AiOnlineConsultTestSamples.VA_USER_ID, "甲", null);
        verify(consultUserMapper, never()).insertBizConsultUser(any());
    }

    @Test
    @DisplayName("提问+AI回复")
    void sendMessage()
    {
        when(sessionMapper.selectAiSessionById(AiOnlineConsultTestSamples.SESSION_ID))
                .thenReturn(AiOnlineConsultTestSamples.activeAiSession());
        VetConsultSendBody body = new VetConsultSendBody();
        body.setMsgType(ConsultSessionRules.MSG_TYPE_TEXT);
        body.setContent("牛不吃草");
        AiConsultSendResult result = service.sendAiMessage(AiOnlineConsultTestSamples.SESSION_ID, body,
                AiOnlineConsultTestSamples.VA_USER_ID, "甲", null);
        assertNotNull(result.getUserMessage());
        assertNotNull(result.getAiMessage());
        assertEquals(ConsultSessionRules.SENDER_ROLE_AI, result.getAiMessage().getSenderRole());
        verify(consultPushService, atLeastOnce()).publishNewMessage(any());
        verify(consultUserMapper, atLeastOnce()).updateLastUseTime(eq(AiOnlineConsultTestSamples.VA_USER_ID), any());
    }

    @Test
    @DisplayName("客户端大模型回复仅落库")
    void sendWithClientAiReply()
    {
        when(sessionMapper.selectAiSessionById(AiOnlineConsultTestSamples.SESSION_ID))
                .thenReturn(AiOnlineConsultTestSamples.activeAiSession());
        VetConsultSendBody body = new VetConsultSendBody();
        body.setMsgType(ConsultSessionRules.MSG_TYPE_TEXT);
        body.setContent("牛不吃草");
        body.setAiReplyContent("请先观察体温");
        AiConsultSendResult result = service.sendAiMessage(AiOnlineConsultTestSamples.SESSION_ID, body,
                AiOnlineConsultTestSamples.VA_USER_ID, "甲", null);
        assertEquals("请先观察体温", result.getAiMessage().getContent());
        verifyNoInteractions(consultAiGateway);
    }

    @Test
    @DisplayName("保存大模型 real_session_id")
    void sendWithRealSessionId()
    {
        when(sessionMapper.selectAiSessionById(AiOnlineConsultTestSamples.SESSION_ID))
                .thenReturn(AiOnlineConsultTestSamples.activeAiSession());
        VetConsultSendBody body = new VetConsultSendBody();
        body.setMsgType(ConsultSessionRules.MSG_TYPE_TEXT);
        body.setContent("牛不吃草");
        body.setAiReplyContent("请先观察体温");
        body.setRealSessionId("gateway-session-001");
        service.sendAiMessage(AiOnlineConsultTestSamples.SESSION_ID, body,
                AiOnlineConsultTestSamples.VA_USER_ID, "甲", null);
        verify(sessionMapper).updateRealSessionId(AiOnlineConsultTestSamples.SESSION_ID, "gateway-session-001");
    }

    @Test
    @DisplayName("AI消息保存 cost_time")
    void sendWithCostTime()
    {
        when(sessionMapper.selectAiSessionById(AiOnlineConsultTestSamples.SESSION_ID))
                .thenReturn(AiOnlineConsultTestSamples.activeAiSession());
        VetConsultSendBody body = new VetConsultSendBody();
        body.setMsgType(ConsultSessionRules.MSG_TYPE_TEXT);
        body.setContent("牛不吃草");
        body.setAiReplyContent("请先观察体温");
        body.setCostTime(1523L);
        service.sendAiMessage(AiOnlineConsultTestSamples.SESSION_ID, body,
                AiOnlineConsultTestSamples.VA_USER_ID, "甲", null);
        verify(messageMapper).insertBizConsultMessage(argThat(m -> m != null
                && Long.valueOf(1523L).equals(m.getCostTime())
                && Integer.valueOf(ConsultSessionRules.SENDER_ROLE_AI).equals(m.getSenderRole())));
    }

    @Test
    @DisplayName("提问消息保存 ai_category")
    void sendWithAiCategory()
    {
        when(sessionMapper.selectAiSessionById(AiOnlineConsultTestSamples.SESSION_ID))
                .thenReturn(AiOnlineConsultTestSamples.activeAiSession());
        VetConsultSendBody body = new VetConsultSendBody();
        body.setMsgType(ConsultSessionRules.MSG_TYPE_TEXT);
        body.setContent("牛不吃草");
        body.setAiReplyContent("请先观察体温");
        body.setAiCategory("2");
        service.sendAiMessage(AiOnlineConsultTestSamples.SESSION_ID, body,
                AiOnlineConsultTestSamples.VA_USER_ID, "甲", "192.168.1.10");
        verify(messageMapper).insertBizConsultMessage(argThat(m -> m != null
                && "2".equals(m.getAiCategory())
                && "192.168.1.10".equals(m.getAskerIp())
                && Integer.valueOf(ConsultSessionRules.SENDER_ROLE_ASKER).equals(m.getSenderRole())));
    }

    @Test
    @DisplayName("提问消息保存 asker_ip")
    void sendWithAskerIp()
    {
        when(sessionMapper.selectAiSessionById(AiOnlineConsultTestSamples.SESSION_ID))
                .thenReturn(AiOnlineConsultTestSamples.activeAiSession());
        VetConsultSendBody body = new VetConsultSendBody();
        body.setMsgType(ConsultSessionRules.MSG_TYPE_TEXT);
        body.setContent("牛不吃草");
        body.setAiReplyContent("请先观察体温");
        service.sendAiMessage(AiOnlineConsultTestSamples.SESSION_ID, body,
                AiOnlineConsultTestSamples.VA_USER_ID, "甲", "10.0.0.8");
        verify(messageMapper).insertBizConsultMessage(argThat(m -> m != null
                && "10.0.0.8".equals(m.getAskerIp())
                && Integer.valueOf(ConsultSessionRules.SENDER_ROLE_ASKER).equals(m.getSenderRole())));
    }

    @Test
    @DisplayName("处理中拒发")
    void processingLock()
    {
        when(sessionMapper.selectAiSessionById(AiOnlineConsultTestSamples.SESSION_ID))
                .thenReturn(AiOnlineConsultTestSamples.activeAiSession());
        when(processingLock.tryLock(AiOnlineConsultTestSamples.SESSION_ID)).thenReturn(false);
        VetConsultSendBody body = new VetConsultSendBody();
        body.setMsgType(ConsultSessionRules.MSG_TYPE_TEXT);
        body.setContent("x");
        ServiceException ex = assertThrows(ServiceException.class,
                () -> service.sendAiMessage(AiOnlineConsultTestSamples.SESSION_ID, body,
                        AiOnlineConsultTestSamples.VA_USER_ID, "甲", null));
        assertEquals("AI 正在回复中，请稍候", ex.getMessage());
    }

    @Test
    @DisplayName("越权会话")
    void forbiddenSession()
    {
        when(sessionMapper.selectAiSessionById(AiOnlineConsultTestSamples.OTHER_SESSION_ID))
                .thenReturn(AiOnlineConsultTestSamples.otherUserSession());
        ServiceException ex = assertThrows(ServiceException.class,
                () -> service.selectSessionMessages(AiOnlineConsultTestSamples.OTHER_SESSION_ID, null, 50,
                        AiOnlineConsultTestSamples.VA_USER_ID));
        assertEquals("无权查看该会话", ex.getMessage());
    }

    @Test
    @DisplayName("hide后会话")
    void hiddenSession()
    {
        when(sessionMapper.selectAiSessionById(AiOnlineConsultTestSamples.SESSION_ID))
                .thenReturn(AiOnlineConsultTestSamples.hiddenAiSession());
        ServiceException ex = assertThrows(ServiceException.class,
                () -> service.selectSessionMessages(AiOnlineConsultTestSamples.SESSION_ID, null, 50,
                        AiOnlineConsultTestSamples.VA_USER_ID));
        assertEquals("会话不存在或已删除", ex.getMessage());
    }

    @Test
    @DisplayName("快捷模板")
    void promptTemplate()
    {
        AiConsultOpenBody body = new AiConsultOpenBody();
        body.setCategory(AiConsultConstants.CATEGORY_DISEASE);
        assertNotNull(service.createAiSession(AiOnlineConsultTestSamples.VA_USER_ID, "甲", body)
                .getPromptTemplate());
    }

    @Test
    @DisplayName("hide更新consult_type=2")
    void hideSession()
    {
        when(sessionMapper.updateSessionVisible(eq(AiOnlineConsultTestSamples.SESSION_ID),
                eq(AiOnlineConsultTestSamples.VA_USER_ID), eq(ConsultSessionRules.CONSULT_TYPE_AI),
                eq(ConsultSessionRules.VET_VISIBLE_NO))).thenReturn(1);
        service.hideSession(AiOnlineConsultTestSamples.SESSION_ID, AiOnlineConsultTestSamples.VA_USER_ID);
        verify(sessionMapper).updateSessionVisible(eq(AiOnlineConsultTestSamples.SESSION_ID),
                eq(AiOnlineConsultTestSamples.VA_USER_ID), eq(ConsultSessionRules.CONSULT_TYPE_AI),
                eq(ConsultSessionRules.VET_VISIBLE_NO));
    }
}
