package com.ruoyi.ccdi.project.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
import com.ruoyi.common.exception.ServiceException;
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 java.math.BigDecimal;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
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.argThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class CcdiProjectOverviewServiceSuspiciousTransactionTest {

    @InjectMocks
    private CcdiProjectOverviewServiceImpl service;

    @Mock
    private CcdiProjectOverviewMapper overviewMapper;

    @Mock
    private CcdiProjectMapper projectMapper;

    @Mock
    private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper;

    @Mock
    private CcdiBankTagResultMapper bankTagResultMapper;

    @Mock
    private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;

    @Test
    void shouldReturnDeduplicatedSuspiciousTransactions() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        when(projectMapper.selectById(40L)).thenReturn(project);

        CcdiProjectSuspiciousTransactionItemVO item = new CcdiProjectSuspiciousTransactionItemVO();
        item.setBankStatementId(101L);
        item.setSuspiciousPersonName("王五");
        item.setRelatedPersonName("孙七");
        item.setRelatedStaffName("孙七");
        item.setRelatedStaffCode("809901");
        item.setRelationType("配偶");
        item.setUserMemo("零钱商户消费");
        item.setCashType("转账");
        item.setDisplayAmount(new BigDecimal("200000.00"));
        item.setHasModelRuleHit(true);
        item.setHasNameListHit(true);

        Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>(1, 10);
        page.setRecords(List.of(item));
        page.setTotal(1);
        when(overviewMapper.selectSuspiciousTransactionPage(any(Page.class), any(CcdiProjectSuspiciousTransactionQueryDTO.class)))
            .thenReturn(page);

        CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
        queryDTO.setProjectId(40L);
        queryDTO.setSuspiciousType("name_list");
        queryDTO.setPageNum(1);
        queryDTO.setPageSize(10);

        CcdiProjectSuspiciousTransactionPageVO result = service.getSuspiciousTransactions(queryDTO);

        assertEquals(1, result.getRows().size());
        assertEquals(1L, result.getTotal());
        assertTrue(result.getRows().getFirst().getHasModelRuleHit());
        assertTrue(result.getRows().getFirst().getHasNameListHit());
        verify(overviewMapper).selectSuspiciousTransactionPage(
            any(Page.class),
            argThat(query -> "NAME_LIST".equals(query.getSuspiciousType()))
        );
    }

    @Test
    void shouldIncludeExternalBranchWhenExternalSubjectExistsForAllSuspiciousTransactions() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(overviewMapper.selectExternalPersonSubjectExistsByProjectId(40L)).thenReturn(1);

        Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>(1, 10);
        page.setRecords(List.of());
        page.setTotal(0);
        when(overviewMapper.selectSuspiciousTransactionPage(any(Page.class), any(CcdiProjectSuspiciousTransactionQueryDTO.class)))
            .thenReturn(page);

        CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
        queryDTO.setProjectId(40L);

        CcdiProjectSuspiciousTransactionPageVO result = service.getSuspiciousTransactions(queryDTO);

        assertEquals(0L, result.getTotal());
        verify(overviewMapper).selectSuspiciousTransactionPage(
            any(Page.class),
            argThat(query -> "ALL".equals(query.getSuspiciousType())
                && Boolean.TRUE.equals(query.getIncludeExternalPerson()))
        );
    }

    @Test
    void shouldSkipExternalBranchAndAttachSelectedEmployeeModelTags() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        when(projectMapper.selectById(40L)).thenReturn(project);

        CcdiProjectSuspiciousTransactionItemVO item = new CcdiProjectSuspiciousTransactionItemVO();
        item.setBankStatementId(101L);
        Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>(1, 10);
        page.setRecords(List.of(item));
        page.setTotal(1);
        when(overviewMapper.selectSuspiciousTransactionPage(
            any(Page.class), any(CcdiProjectSuspiciousTransactionQueryDTO.class)
        )).thenReturn(page);

        CcdiBankStatementHitTagVO tag = new CcdiBankStatementHitTagVO();
        tag.setBankStatementId(101L);
        tag.setModelCode("SUSPICIOUS_GAMBLING");
        tag.setRuleName("疑似赌博");
        when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
            40L, List.of(101L), "SUSPICIOUS_GAMBLING", "ALL"
        )).thenReturn(List.of(tag));

        CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
        queryDTO.setProjectId(40L);
        queryDTO.setModelCode("suspicious_gambling");
        queryDTO.setSuspiciousType("all");

        CcdiProjectSuspiciousTransactionPageVO result = service.getSuspiciousTransactions(queryDTO);

        assertEquals("疑似赌博", result.getRows().getFirst().getHitTags().getFirst().getRuleName());
        verify(overviewMapper, never()).selectExternalPersonSubjectExistsByProjectId(40L);
        verify(overviewMapper).selectSuspiciousTransactionPage(
            any(Page.class),
            argThat(query -> !Boolean.TRUE.equals(query.getIncludeExternalPerson()))
        );
    }

    @Test
    void shouldKeepEmployeeRuleScopeSeparateFromExternalModels() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        when(projectMapper.selectById(40L)).thenReturn(project);

        Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>(1, 10);
        page.setRecords(List.of());
        page.setTotal(0);
        when(overviewMapper.selectSuspiciousTransactionPage(
            any(Page.class), any(CcdiProjectSuspiciousTransactionQueryDTO.class)
        )).thenReturn(page);

        CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
        queryDTO.setProjectId(40L);
        queryDTO.setSuspiciousType("MODEL_RULE");

        service.getSuspiciousTransactions(queryDTO);

        verify(overviewMapper, never()).selectExternalPersonSubjectExistsByProjectId(40L);
        verify(overviewMapper).selectSuspiciousTransactionPage(
            any(Page.class),
            argThat(query -> "MODEL_RULE".equals(query.getSuspiciousType())
                && !Boolean.TRUE.equals(query.getIncludeExternalPerson()))
        );
    }

    @Test
    void shouldReturnEmptyExternalSuspiciousTransactionsWhenExternalSubjectDoesNotExist() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(overviewMapper.selectExternalPersonSubjectExistsByProjectId(40L)).thenReturn(null);

        CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
        queryDTO.setProjectId(40L);
        queryDTO.setSuspiciousType("external_person");

        CcdiProjectSuspiciousTransactionPageVO result = service.getSuspiciousTransactions(queryDTO);

        assertEquals(0L, result.getTotal());
        assertTrue(result.getRows().isEmpty());
        verify(overviewMapper, never()).selectSuspiciousTransactionPage(
            any(Page.class),
            any(CcdiProjectSuspiciousTransactionQueryDTO.class)
        );
    }

    @Test
    void shouldReturnEmptyExternalSuspiciousTransactionExportWhenExternalSubjectDoesNotExist() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(overviewMapper.selectExternalPersonSubjectExistsByProjectId(40L)).thenReturn(null);

        CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
        queryDTO.setProjectId(40L);
        queryDTO.setSuspiciousType("EXTERNAL_PERSON");

        List<CcdiProjectSuspiciousTransactionExcel> rows = service.exportSuspiciousTransactions(queryDTO);

        assertTrue(rows.isEmpty());
        verify(overviewMapper, never()).selectReportSuspiciousTransactionList(any(CcdiProjectSuspiciousTransactionQueryDTO.class));
    }

    @Test
    void shouldExportSuspiciousTransactionsWithCurrentFilter() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        when(projectMapper.selectById(40L)).thenReturn(project);

        CcdiProjectOverviewReportSuspiciousTransactionVO item =
            new CcdiProjectOverviewReportSuspiciousTransactionVO();
        item.setTrxDate("2024-01-15 10:00:00");
        item.setLeAccountNo("6222000000000000");
        item.setLeAccountName("孙七");
        item.setCustomerAccountName("测试对手方");
        item.setCustomerAccountNo("6222000000000001");
        item.setRelatedStaffName("孙七");
        item.setRelatedStaffCode("809901");
        item.setUserMemo("");
        item.setCashType("转账");
        item.setHitTags("大额交易");
        item.setDisplayAmount(new BigDecimal("500000.00"));
        when(overviewMapper.selectReportSuspiciousTransactionList(any(CcdiProjectSuspiciousTransactionQueryDTO.class)))
            .thenReturn(List.of(item));

        CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
        queryDTO.setProjectId(40L);

        List<CcdiProjectSuspiciousTransactionExcel> rows = service.exportSuspiciousTransactions(queryDTO);

        assertEquals(1, rows.size());
        assertEquals("6222000000000000", rows.getFirst().getLeAccountNo());
        assertEquals("孙七", rows.getFirst().getLeAccountName());
        assertEquals("测试对手方", rows.getFirst().getCustomerAccountName());
        assertEquals("6222000000000001", rows.getFirst().getCustomerAccountNo());
        assertEquals("孙七(809901)", rows.getFirst().getRelatedStaffDisplay());
        assertEquals("", rows.getFirst().getUserMemo());
        assertEquals("转账", rows.getFirst().getCashType());
        assertEquals("大额交易", rows.getFirst().getHitTags());
    }

    @Test
    void shouldThrowWhenSuspiciousTransactionProjectDoesNotExist() {
        when(projectMapper.selectById(99L)).thenReturn(null);

        CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
        queryDTO.setProjectId(99L);

        assertThrows(ServiceException.class, () -> service.getSuspiciousTransactions(queryDTO));
        assertThrows(ServiceException.class, () -> service.exportSuspiciousTransactions(queryDTO));
    }
}
