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

import com.ruoyi.ccdi.project.domain.CcdiModelParam;
import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
import com.ruoyi.ccdi.project.domain.dto.ModelParamGroupDTO;
import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveDTO;
import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveAllDTO;
import com.ruoyi.ccdi.project.domain.dto.ParamValueItem;
import com.ruoyi.ccdi.project.domain.vo.ModelParamAllVO;
import com.ruoyi.ccdi.project.mapper.CcdiModelParamMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
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.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;

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

@ExtendWith(MockitoExtension.class)
class CcdiModelParamServiceImplTest {

    @InjectMocks
    private CcdiModelParamServiceImpl service;

    @Mock
    private CcdiModelParamMapper modelParamMapper;

    @Mock
    private CcdiProjectMapper projectMapper;

    @Mock
    private ICcdiProjectService projectService;

    @Mock
    private ICcdiBankTagService bankTagService;

    @Test
    void selectAllParams_shouldReadSystemDefaultsForDefaultProject() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(100L);
        project.setConfigType("default");
        when(projectMapper.selectById(100L)).thenReturn(project);
        when(modelParamMapper.selectByProjectId(0L)).thenReturn(List.of(
            buildParam(2L, 0L, "SUSPICIOUS_PART_TIME", "模型B", "P2", "2"),
            buildParam(1L, 0L, "LARGE_TRANSACTION", "模型A", "P1", "1")
        ));

        ModelParamAllVO result = service.selectAllParams(100L);

        verify(modelParamMapper).selectByProjectId(0L);
        assertEquals(2, result.getModels().size());
        assertEquals("LARGE_TRANSACTION", result.getModels().get(0).getModelCode());
        assertEquals("SUSPICIOUS_PART_TIME", result.getModels().get(1).getModelCode());
    }

    @Test
    @SuppressWarnings("unchecked")
    void saveParams_shouldCopyAllSystemDefaultsForDefaultProjectOnFirstSave() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(123L);
        project.setConfigType("default");
        when(projectMapper.selectById(123L)).thenReturn(project);
        when(modelParamMapper.selectByProjectId(0L)).thenReturn(List.of(
            buildParam(1L, 0L, "LARGE_TRANSACTION", "大额交易模型", "SINGLE_TRANSACTION_AMOUNT", "1111"),
            buildParam(2L, 0L, "SUSPICIOUS_GAMBLING", "疑似赌博交易模型", "MULTI_PARTY_AMT_MIN", "500")
        ));
        when(modelParamMapper.insertBatch(anyList())).thenReturn(2);
        when(modelParamMapper.updateParamValue(123L, "LARGE_TRANSACTION", "SINGLE_TRANSACTION_AMOUNT", "2222", "admin"))
            .thenReturn(1);

        ModelParamSaveDTO saveDTO = new ModelParamSaveDTO();
        saveDTO.setProjectId(123L);
        saveDTO.setModelCode("LARGE_TRANSACTION");
        ModelParamSaveDTO.ParamValueItem item = new ModelParamSaveDTO.ParamValueItem();
        item.setParamCode("SINGLE_TRANSACTION_AMOUNT");
        item.setParamValue("2222");
        saveDTO.setParams(List.of(item));

        try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
            mocked.when(SecurityUtils::getUsername).thenReturn("admin");

            service.saveParams(saveDTO);
        }

        ArgumentCaptor<List<CcdiModelParam>> captor = ArgumentCaptor.forClass(List.class);
        verify(modelParamMapper).insertBatch(captor.capture());
        List<CcdiModelParam> copiedParams = captor.getValue();
        assertEquals(2, copiedParams.size());
        assertTrue(copiedParams.stream().allMatch(param -> Long.valueOf(123L).equals(param.getProjectId())));
        assertEquals("custom", project.getConfigType());
        verify(projectMapper).updateById(project);
        verify(modelParamMapper).updateParamValue(123L, "LARGE_TRANSACTION", "SINGLE_TRANSACTION_AMOUNT", "2222", "admin");
    }

    @Test
    void shouldRejectSaveAllParamsWhenProjectIsTagging() {
        org.mockito.Mockito.doThrow(new ServiceException("当前项目正在进行银行流水打标，暂不允许修改参数"))
            .when(projectService).ensureProjectWritable(40L, "当前项目正在进行银行流水打标，暂不允许修改参数");

        assertThrows(ServiceException.class, () -> service.saveAllParams(buildSaveAllDto()));
    }

    @Test
    void shouldRejectSaveParamsWhenProjectIsTagging() {
        ModelParamSaveDTO saveDTO = new ModelParamSaveDTO();
        saveDTO.setProjectId(40L);
        saveDTO.setModelCode("LARGE_TRANSACTION");
        ModelParamSaveDTO.ParamValueItem item = new ModelParamSaveDTO.ParamValueItem();
        item.setParamCode("SINGLE_TRANSACTION_AMOUNT");
        item.setParamValue("2000");
        saveDTO.setParams(List.of(item));

        org.mockito.Mockito.doThrow(new ServiceException("当前项目正在进行银行流水打标，暂不允许修改参数"))
            .when(projectService).ensureProjectWritable(40L, "当前项目正在进行银行流水打标，暂不允许修改参数");

        assertThrows(ServiceException.class, () -> service.saveParams(saveDTO));
    }

    @Test
    void saveAllParams_shouldSubmitAutoRebuildAfterProjectParamsUpdated() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("custom");
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(modelParamMapper.selectOne(any())).thenReturn(
            buildParam(1L, 40L, "LARGE_TRANSACTION", "大额交易模型", "SINGLE_TRANSACTION_AMOUNT", "1000")
        );

        try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
            mocked.when(SecurityUtils::getUsername).thenReturn("admin");

            service.saveAllParams(buildSaveAllDto());
        }

        verify(projectService).ensureProjectNotArchived(40L, "已归档项目暂不允许修改参数");
        verify(bankTagService).submitAutoRebuild(40L, TriggerType.AUTO_PARAM_CHANGE);
    }

    @Test
    void saveParams_shouldSubmitAutoRebuildAfterProjectParamsUpdated() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("custom");
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(modelParamMapper.updateParamValue(40L, "LARGE_TRANSACTION", "SINGLE_TRANSACTION_AMOUNT", "2000", "admin"))
            .thenReturn(1);

        ModelParamSaveDTO saveDTO = new ModelParamSaveDTO();
        saveDTO.setProjectId(40L);
        saveDTO.setModelCode("LARGE_TRANSACTION");
        ModelParamSaveDTO.ParamValueItem item = new ModelParamSaveDTO.ParamValueItem();
        item.setParamCode("SINGLE_TRANSACTION_AMOUNT");
        item.setParamValue("2000");
        saveDTO.setParams(List.of(item));

        try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
            mocked.when(SecurityUtils::getUsername).thenReturn("admin");

            service.saveParams(saveDTO);
        }

        verify(projectService).ensureProjectNotArchived(40L, "已归档项目暂不允许修改参数");
        verify(bankTagService).submitAutoRebuild(40L, TriggerType.AUTO_PARAM_CHANGE);
    }

    @Test
    void saveParams_shouldNotSubmitAutoRebuildWhenNoProjectParamUpdated() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("custom");
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(modelParamMapper.updateParamValue(40L, "LARGE_TRANSACTION", "SINGLE_TRANSACTION_AMOUNT", "2000", "admin"))
            .thenReturn(0);

        ModelParamSaveDTO saveDTO = new ModelParamSaveDTO();
        saveDTO.setProjectId(40L);
        saveDTO.setModelCode("LARGE_TRANSACTION");
        ModelParamSaveDTO.ParamValueItem item = new ModelParamSaveDTO.ParamValueItem();
        item.setParamCode("SINGLE_TRANSACTION_AMOUNT");
        item.setParamValue("2000");
        saveDTO.setParams(List.of(item));

        try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
            mocked.when(SecurityUtils::getUsername).thenReturn("admin");

            service.saveParams(saveDTO);
        }

        verify(bankTagService, never()).submitAutoRebuild(anyLong(), any());
    }

    @Test
    void saveAllParams_shouldNotSubmitAutoRebuildForGlobalDefaults() {
        ModelParamSaveAllDTO saveAllDTO = buildSaveAllDto();
        saveAllDTO.setProjectId(0L);
        when(modelParamMapper.selectOne(any())).thenReturn(
            buildParam(1L, 0L, "LARGE_TRANSACTION", "大额交易模型", "SINGLE_TRANSACTION_AMOUNT", "1000")
        );

        try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
            mocked.when(SecurityUtils::getUsername).thenReturn("admin");

            service.saveAllParams(saveAllDTO);
        }

        verify(bankTagService, never()).submitAutoRebuild(any(), any());
    }

    private CcdiModelParam buildParam(
        Long id,
        Long projectId,
        String modelCode,
        String modelName,
        String paramCode,
        String paramValue
    ) {
        CcdiModelParam param = new CcdiModelParam();
        param.setId(id);
        param.setProjectId(projectId);
        param.setModelCode(modelCode);
        param.setModelName(modelName);
        param.setParamCode(paramCode);
        param.setParamName(paramCode);
        param.setParamValue(paramValue);
        param.setSortOrder(1);
        return param;
    }

    private ModelParamSaveAllDTO buildSaveAllDto() {
        ParamValueItem item = new ParamValueItem();
        item.setParamCode("SINGLE_TRANSACTION_AMOUNT");
        item.setParamValue("2000");

        ModelParamGroupDTO group = new ModelParamGroupDTO();
        group.setModelCode("LARGE_TRANSACTION");
        group.setParams(List.of(item));

        ModelParamSaveAllDTO dto = new ModelParamSaveAllDTO();
        dto.setProjectId(40L);
        dto.setModels(List.of(group));
        return dto;
    }
}
