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

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementOptionVO;
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
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.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class CcdiBankStatementServiceImplTest {

    @InjectMocks
    private CcdiBankStatementServiceImpl service;

    @Mock
    private CcdiBankStatementMapper bankStatementMapper;

    @Mock
    private CcdiBankTagResultMapper bankTagResultMapper;

    @Test
    void getFilterOptions_shouldReturnProjectWideOptions() {
        CcdiBankStatementFilterOptionsVO options = new CcdiBankStatementFilterOptionsVO();
        options.setOurSubjectOptions(List.of(new CcdiBankStatementOptionVO("主体A", "主体A")));
        when(bankStatementMapper.selectFilterOptions(100L)).thenReturn(options);

        CcdiBankStatementFilterOptionsVO result = service.getFilterOptions(100L);

        assertEquals(1, result.getOurSubjectOptions().size());
    }

    @Test
    void pageQuery_shouldNormalizeSortFieldAndDirection() {
        Page<CcdiBankStatementListVO> page = new Page<>(1, 10);
        CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
        queryDTO.setProjectId(100L);
        queryDTO.setOrderBy("amount");
        queryDTO.setOrderDirection("desc");
        page.setRecords(List.of(new CcdiBankStatementListVO()));
        doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));

        service.selectStatementPage(page, queryDTO);

        verify(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));
    }

    @Test
    void normalizeQuery_shouldFallbackToSafeDefaults() {
        Page<CcdiBankStatementListVO> page = new Page<>(1, 10);
        CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
        queryDTO.setProjectId(100L);
        queryDTO.setOrderBy("drop table");
        queryDTO.setOrderDirection("sideways");
        doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));

        service.selectStatementPage(page, queryDTO);

        assertEquals("trxDate", queryDTO.getOrderBy());
        assertEquals("desc", queryDTO.getOrderDirection());
        assertEquals("all", queryDTO.getTabType());
    }

    @Test
    void normalizeQuery_shouldTrimBlankStringsToNull() {
        Page<CcdiBankStatementListVO> page = new Page<>(1, 10);
        CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
        queryDTO.setProjectId(100L);
        queryDTO.setCounterpartyName("   ");
        queryDTO.setUserMemo("  ");
        queryDTO.setCounterpartyAccount(" ");
        queryDTO.setTransactionType("   ");
        doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));

        service.selectStatementPage(page, queryDTO);

        assertNull(queryDTO.getCounterpartyName());
        assertNull(queryDTO.getUserMemo());
        assertNull(queryDTO.getCounterpartyAccount());
        assertNull(queryDTO.getTransactionType());
    }

    @Test
    void selectStatementListForExport_shouldMapDisplayColumns() {
        CcdiBankStatementListVO row = new CcdiBankStatementListVO();
        row.setTrxDate("2024-02-01 10:33:44");
        row.setLeAccountNo("6222");
        row.setLeAccountName("张三");
        row.setDisplayAmount(new BigDecimal("-8.00"));
        when(bankStatementMapper.selectStatementListForExport(any())).thenReturn(List.of(row));

        List<CcdiBankStatementExcel> result = service.selectStatementListForExport(new CcdiBankStatementQueryDTO());

        assertEquals(1, result.size());
        assertEquals("6222", result.get(0).getLeAccountNo());
    }

    @Test
    void selectStatementListForExport_shouldIncludeHitTagNames() {
        CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
        queryDTO.setProjectId(43L);
        CcdiBankStatementListVO row = new CcdiBankStatementListVO();
        row.setBankStatementId(51274L);
        row.setLeAccountNo("6222");
        when(bankStatementMapper.selectStatementListForExport(same(queryDTO))).thenReturn(List.of(row));

        CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
        hitTag.setBankStatementId(51274L);
        hitTag.setRuleName("大额存现交易");
        when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
            43L, List.of(51274L), null, null
        ))
                .thenReturn(List.of(hitTag));

        List<CcdiBankStatementExcel> result = service.selectStatementListForExport(queryDTO);

        assertEquals(1, result.size());
        assertEquals("大额存现交易", result.get(0).getHitTags());
    }

    @Test
    void selectStatementPage_shouldAttachHitTags() {
        Page<CcdiBankStatementListVO> page = new Page<>(1, 10);
        CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
        queryDTO.setProjectId(43L);
        CcdiBankStatementListVO row = new CcdiBankStatementListVO();
        row.setBankStatementId(51274L);
        page.setRecords(List.of(row));
        doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));

        CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
        hitTag.setBankStatementId(51274L);
        hitTag.setRuleCode("LARGE_CASH_DEPOSIT");
        hitTag.setRuleName("大额存现交易");
        when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
            43L, List.of(51274L), null, null
        ))
                .thenReturn(List.of(hitTag));

        Page<CcdiBankStatementListVO> result = service.selectStatementPage(page, queryDTO);

        assertEquals(1, result.getRecords().get(0).getHitTags().size());
        assertEquals("LARGE_CASH_DEPOSIT", result.getRecords().get(0).getHitTags().get(0).getRuleCode());
    }

    @Test
    void getStatementDetail_shouldDelegateToMapper() {
        CcdiBankStatementDetailVO detailVO = new CcdiBankStatementDetailVO();
        detailVO.setBankStatementId(200L);
        detailVO.setProjectId(43L);
        when(bankStatementMapper.selectStatementDetailById(200L)).thenReturn(detailVO);
        CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
        hitTag.setBankStatementId(200L);
        hitTag.setRuleName("大额存现交易");
        when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
            43L, List.of(200L), null, null
        ))
                .thenReturn(List.of(hitTag));

        CcdiBankStatementDetailVO result = service.getStatementDetail(200L);

        assertSame(detailVO, result);
        assertEquals(1, result.getHitTags().size());
        assertEquals("大额存现交易", result.getHitTags().get(0).getRuleName());
    }

    @Test
    void getStatementDetail_shouldApplySuspiciousTransactionScope() {
        CcdiBankStatementDetailVO detailVO = new CcdiBankStatementDetailVO();
        detailVO.setBankStatementId(200L);
        detailVO.setProjectId(43L);
        when(bankStatementMapper.selectStatementDetailById(200L)).thenReturn(detailVO);
        when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
            43L, List.of(200L), "SUSPICIOUS_GAMBLING", "MODEL_RULE"
        )).thenReturn(List.of());

        service.getStatementDetail(200L, "suspicious_gambling", "model_rule");

        verify(bankTagResultMapper).selectStatementTagsByProjectAndStatementIds(
            43L, List.of(200L), "SUSPICIOUS_GAMBLING", "MODEL_RULE"
        );
    }
}
