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

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.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
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 com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.kb.support.KbApiProperties;
import com.ruoyi.web.kb.support.KbMarkdownGenerator;
import com.ruoyi.web.kb.service.KnowledgeBaseClient;
import com.ruoyi.web.modules.architecture.support.WithdrawalPeriodStandardKbMarkdown;
import com.ruoyi.web.modules.architecture.domain.BizWithdrawalPeriodStandard;
import com.ruoyi.web.modules.architecture.mapper.BizWithdrawalPeriodStandardMapper;

@ExtendWith(MockitoExtension.class)
@DisplayName("BizWithdrawalPeriodStandardServiceImpl")
class BizWithdrawalPeriodStandardServiceImplTest
{

    @Mock
    private BizWithdrawalPeriodStandardMapper mapper;

    @Mock
    private KnowledgeBaseClient knowledgeBaseClient;

    @Mock
    private KbApiProperties kbApiProperties;

    @Mock
    private WithdrawalPeriodStandardKbMarkdown withdrawalPeriodKbMarkdown;

    @Mock
    private KbMarkdownGenerator kbMarkdownGenerator;

    @InjectMocks
    private BizWithdrawalPeriodStandardServiceImpl service;

    @Test
    @DisplayName("药物名称重复禁止新增")
    void insertDuplicateDrugName()
    {
        when(mapper.countByDrugNameExcludeId("药", null)).thenReturn(1);
        assertThrows(ServiceException.class, () -> service.insertBizWithdrawalPeriodStandard(sampleBody()));
        verify(mapper, never()).insertBizWithdrawalPeriodStandard(any());
    }

    @Test
    @DisplayName("药物名称重复禁止修改")
    void updateDuplicateDrugName()
    {
        when(mapper.countByDrugNameExcludeId("新名", 11L)).thenReturn(1);

        BizWithdrawalPeriodStandard incoming = sampleBody();
        incoming.setId(11L);
        incoming.setDrugName("新名");

        assertThrows(ServiceException.class, () -> service.updateBizWithdrawalPeriodStandard(incoming));
        verify(mapper, never()).updateBizWithdrawalPeriodStandard(any());
    }

    @Test
    @DisplayName("新增落库未同步")
    void insert()
    {
        when(mapper.insertBizWithdrawalPeriodStandard(any())).thenAnswer(inv -> {
            BizWithdrawalPeriodStandard r = inv.getArgument(0);
            r.setId(100L);
            return 1;
        });

        BizWithdrawalPeriodStandard row = sampleBody();
        assertEquals(1, service.insertBizWithdrawalPeriodStandard(row));
        verify(mapper).insertBizWithdrawalPeriodStandard(argThat(r ->
                r.getKbSyncStatus() == 0 && r.getKbDocId() == null));
    }

    @Test
    @DisplayName("已同步禁止修改")
    void updateSyncedThrows()
    {
        BizWithdrawalPeriodStandard db = sampleBody();
        db.setId(9L);
        db.setKbSyncStatus(1);
        db.setKbDocId("LOCAL-9");
        when(mapper.selectBizWithdrawalPeriodStandardById(9L)).thenReturn(db);

        BizWithdrawalPeriodStandard incoming = sampleBody();
        incoming.setId(9L);
        incoming.setDrugName("改名");

        assertThrows(ServiceException.class, () -> service.updateBizWithdrawalPeriodStandard(incoming));
        verify(mapper, never()).updateBizWithdrawalPeriodStandard(any());
    }

    @Test
    @DisplayName("未同步修改保留 kb 字段")
    void updateUnsynced()
    {
        BizWithdrawalPeriodStandard db = sampleBody();
        db.setId(11L);
        db.setKbSyncStatus(0);
        db.setKbDocId(null);
        when(mapper.selectBizWithdrawalPeriodStandardById(11L)).thenReturn(db);
        when(mapper.countByDrugNameExcludeId("新名", 11L)).thenReturn(0);
        when(mapper.updateBizWithdrawalPeriodStandard(any())).thenReturn(1);

        BizWithdrawalPeriodStandard incoming = sampleBody();
        incoming.setId(11L);
        incoming.setDrugName("新名");
        assertEquals(1, service.updateBizWithdrawalPeriodStandard(incoming));
        verify(mapper).updateBizWithdrawalPeriodStandard(argThat(r ->
                r.getKbSyncStatus() == 0 && r.getKbDocId() == null));
    }

    @Test
    @DisplayName("同步成功写 file_id 并删除临时 md")
    void syncKb()
    {
        BizWithdrawalPeriodStandard db = sampleBody();
        db.setId(3L);
        db.setKbSyncStatus(0);
        Path md = Paths.get("休药期标准_3.md");
        when(mapper.selectBizWithdrawalPeriodStandardById(3L)).thenReturn(db);
        when(withdrawalPeriodKbMarkdown.generateForSync(db)).thenReturn(md);
        when(kbApiProperties.getDefaultKnowledgeBase()).thenReturn("default");
        when(knowledgeBaseClient.uploadFile(any(File.class), eq("休药期标准_3.md"), eq("default"), eq("休药期标准")))
                .thenReturn("FILE-3");
        when(mapper.updateBizWithdrawalPeriodStandard(any())).thenReturn(1);

        assertEquals(1, service.syncKb(3L));
        verify(knowledgeBaseClient).uploadFile(any(File.class), eq("休药期标准_3.md"), eq("default"), eq("休药期标准"));
        verify(kbMarkdownGenerator).deleteQuietly(md);
        verify(mapper).updateBizWithdrawalPeriodStandard(argThat(r ->
                r.getKbSyncStatus() == 1 && "FILE-3".equals(r.getKbDocId())));
    }

    @Test
    @DisplayName("已同步不可再次同步")
    void syncKbWhenSyncedThrows()
    {
        BizWithdrawalPeriodStandard db = sampleBody();
        db.setId(5L);
        db.setKbSyncStatus(1);
        when(mapper.selectBizWithdrawalPeriodStandardById(5L)).thenReturn(db);

        assertThrows(ServiceException.class, () -> service.syncKb(5L));
        verify(mapper, never()).updateBizWithdrawalPeriodStandard(any());
    }

    @Test
    @DisplayName("移出成功")
    void removeKb()
    {
        BizWithdrawalPeriodStandard db = sampleBody();
        db.setId(13L);
        db.setKbSyncStatus(1);
        db.setKbDocId("DOC");
        when(mapper.selectBizWithdrawalPeriodStandardById(13L)).thenReturn(db);
        when(mapper.updateBizWithdrawalPeriodStandard(any())).thenReturn(1);

        assertEquals(1, service.removeKb(13L));
        verify(knowledgeBaseClient).deleteFile("DOC");
        verify(mapper).updateBizWithdrawalPeriodStandard(argThat(r ->
                r.getKbSyncStatus() == 0 && r.getKbDocId() == null));
    }

    @Test
    @DisplayName("已同步禁止删除")
    void deleteSyncedThrows()
    {
        BizWithdrawalPeriodStandard db = sampleBody();
        db.setId(20L);
        db.setKbSyncStatus(1);
        when(mapper.selectBizWithdrawalPeriodStandardById(20L)).thenReturn(db);

        assertThrows(ServiceException.class, () -> service.deleteBizWithdrawalPeriodStandardByIds(new Long[] { 20L }));
        verify(mapper, never()).deleteBizWithdrawalPeriodStandardByIds(any());
    }

    @Test
    @DisplayName("未同步可删")
    void deleteUnsynced()
    {
        BizWithdrawalPeriodStandard db = sampleBody();
        db.setId(21L);
        db.setKbSyncStatus(0);
        when(mapper.selectBizWithdrawalPeriodStandardById(21L)).thenReturn(db);
        when(mapper.deleteBizWithdrawalPeriodStandardByIds(any())).thenReturn(1);

        assertEquals(1, service.deleteBizWithdrawalPeriodStandardByIds(new Long[] { 21L }));
        verify(mapper).deleteBizWithdrawalPeriodStandardByIds(any());
    }

    private static BizWithdrawalPeriodStandard sampleBody()
    {
        BizWithdrawalPeriodStandard r = new BizWithdrawalPeriodStandard();
        r.setDrugName("药");
        r.setDrugType(1);
        r.setWithdrawalDays(7);
        r.setUsageGuide("指南");
        r.setDrugEffect("作用");
        return r;
    }
}
