package com.ruoyi.loanpricing.service.impl;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
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;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.loanpricing.domain.dto.CorporateLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
import com.ruoyi.loanpricing.service.LoanPricingModelService;
import com.ruoyi.loanpricing.service.SensitiveFieldCryptoService;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
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.junit.jupiter.MockitoExtension;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@ExtendWith(MockitoExtension.class)
class LoanPricingWorkflowServiceImplTest
{
    @Mock
    private LoanPricingWorkflowMapper loanPricingWorkflowMapper;

    @Mock
    private LoanPricingModelService loanPricingModelService;

    @Mock
    private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;

    @Mock
    private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;

    @Mock
    private SensitiveFieldCryptoService sensitiveFieldCryptoService;

    @InjectMocks
    private LoanPricingWorkflowServiceImpl loanPricingWorkflowService;

    @BeforeEach
    void setUpLoginUser()
    {
        setLoginUser(1L, "admin", "若依", role(1L, "超级管理员", "admin"));
    }

    @AfterEach
    void clearLoginUser()
    {
        SecurityContextHolder.clearContext();
    }

    @Test
    void shouldKeepCustNameAndIdNumPlainBeforeInsert()
    {
        LoanPricingWorkflow workflow = new LoanPricingWorkflow();
        workflow.setCustName("张三");
        workflow.setIdNum("110101199001011234");
        workflow.setCustIsn("CUST001");
        workflow.setBusinessType("新增");

        loanPricingWorkflowService.createLoanPricing(workflow);

        verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) ->
                Objects.equals("张三", entity.getCustName())
                        && Objects.equals("110101199001011234", entity.getIdNum())
                        && Objects.equals(100L, entity.getDeptId())
                        && Objects.equals("CUST001", entity.getCustIsn())));
        verify(sensitiveFieldCryptoService, never()).encrypt(any());
    }

    @Test
    void shouldReturnPagedWorkflowListWithCalculateRate()
    {
        LoanPricingWorkflowListVO row = new LoanPricingWorkflowListVO();
        row.setCalculateRate("6.15");

        Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
        pageResult.setRecords(java.util.List.of(row));

        when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult);

        IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());

        assertEquals("6.15", result.getRecords().get(0).getCalculateRate());
    }

    @Test
    void shouldReturnPlainCustNameWhenReturningPagedWorkflowList()
    {
        LoanPricingWorkflowListVO row = new LoanPricingWorkflowListVO();
        row.setCustName("张三");

        Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
        pageResult.setRecords(Collections.singletonList(row));

        when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult);

        IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());

        assertEquals("张三", result.getRecords().get(0).getCustName());
        verify(sensitiveFieldCryptoService, never()).decrypt(any());
    }

    @Test
    void shouldNotSetDataScopeCreateByForSuperAdminWhenReturningPagedWorkflowList()
    {
        when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(emptyPageResult());

        loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());

        ArgumentCaptor<LoanPricingWorkflow> queryCaptor = ArgumentCaptor.forClass(LoanPricingWorkflow.class);
        verify(loanPricingWorkflowMapper).selectWorkflowPageWithRates(any(), queryCaptor.capture());

        assertNull(queryCaptor.getValue().getDataScopeCreateBy());
        assertNull(queryCaptor.getValue().getDataScopeDeptId());
    }

    @Test
    void shouldNotSetDataScopeCreateByForBusinessAdminWhenReturningPagedWorkflowList()
    {
        setLoginUser(100L, "8929999", "测试管理员", role(100L, "管理员", "headAdmin"));
        when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(emptyPageResult());

        loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());

        ArgumentCaptor<LoanPricingWorkflow> queryCaptor = ArgumentCaptor.forClass(LoanPricingWorkflow.class);
        verify(loanPricingWorkflowMapper).selectWorkflowPageWithRates(any(), queryCaptor.capture());

        assertNull(queryCaptor.getValue().getDataScopeCreateBy());
        assertNull(queryCaptor.getValue().getDataScopeDeptId());
    }

    @Test
    void shouldSetCurrentDeptForBranchAdminWhenReturningPagedWorkflowList()
    {
        setLoginUser(102L, 101L, "8920100", "测试支行管理员", role(102L, "支行管理员", "branchAdmin"));
        when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(emptyPageResult());

        loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());

        ArgumentCaptor<LoanPricingWorkflow> queryCaptor = ArgumentCaptor.forClass(LoanPricingWorkflow.class);
        verify(loanPricingWorkflowMapper).selectWorkflowPageWithRates(any(), queryCaptor.capture());

        assertEquals(101L, queryCaptor.getValue().getDataScopeDeptId());
        assertNull(queryCaptor.getValue().getDataScopeCreateBy());
    }

    @Test
    void shouldKeepBranchAdminWithinDeptDataScopeWhenCreateByQueryIsProvided()
    {
        setLoginUser(102L, 101L, "8920100", "测试支行管理员", role(102L, "支行管理员", "branchAdmin"));
        LoanPricingWorkflow query = new LoanPricingWorkflow();
        query.setCreateBy("8920001");
        when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(emptyPageResult());

        loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), query);

        ArgumentCaptor<LoanPricingWorkflow> queryCaptor = ArgumentCaptor.forClass(LoanPricingWorkflow.class);
        verify(loanPricingWorkflowMapper).selectWorkflowPageWithRates(any(), queryCaptor.capture());

        assertEquals("8920001", queryCaptor.getValue().getCreateBy());
        assertEquals(101L, queryCaptor.getValue().getDataScopeDeptId());
        assertNull(queryCaptor.getValue().getDataScopeCreateBy());
    }

    @Test
    void shouldSetCurrentCreateByForCustomerManagerWhenReturningPagedWorkflowList()
    {
        setLoginUser(101L, "8920001", "测试客户经理", role(101L, "客户经理", "common"));
        when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(emptyPageResult());

        loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());

        ArgumentCaptor<LoanPricingWorkflow> queryCaptor = ArgumentCaptor.forClass(LoanPricingWorkflow.class);
        verify(loanPricingWorkflowMapper).selectWorkflowPageWithRates(any(), queryCaptor.capture());

        assertEquals("测试客户经理-8920001", queryCaptor.getValue().getDataScopeCreateBy());
    }

    @Test
    void shouldKeepCustomerManagerWithinOwnDataScopeWhenCreateByQueryIsProvided()
    {
        setLoginUser(101L, "8920001", "测试客户经理", role(101L, "客户经理", "common"));
        LoanPricingWorkflow query = new LoanPricingWorkflow();
        query.setCreateBy("若依-admin");
        when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(emptyPageResult());

        loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), query);

        ArgumentCaptor<LoanPricingWorkflow> queryCaptor = ArgumentCaptor.forClass(LoanPricingWorkflow.class);
        verify(loanPricingWorkflowMapper).selectWorkflowPageWithRates(any(), queryCaptor.capture());

        assertEquals("若依-admin", queryCaptor.getValue().getCreateBy());
        assertEquals("测试客户经理-8920001", queryCaptor.getValue().getDataScopeCreateBy());
        assertNull(queryCaptor.getValue().getDataScopeDeptId());
    }

    @Test
    void shouldUseCustIsnAsQueryConditionInLegacyList()
    {
        LoanPricingWorkflow query = new LoanPricingWorkflow();
        query.setCustIsn("CUST001");
        query.setCustName("张三");

        when(loanPricingWorkflowMapper.selectList(any())).thenReturn(Collections.emptyList());

        loanPricingWorkflowService.selectLoanPricingList(query);

        ArgumentCaptor<LambdaQueryWrapper<LoanPricingWorkflow>> wrapperCaptor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
        verify(loanPricingWorkflowMapper).selectList(wrapperCaptor.capture());

        LambdaQueryWrapper<LoanPricingWorkflow> wrapper = wrapperCaptor.getValue();
        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), LoanPricingWorkflow.class);
        String sqlSegment = wrapper.getSqlSegment();

        assertTrue(sqlSegment.contains("cust_isn"), sqlSegment);
        assertTrue(!sqlSegment.contains("cust_name"), sqlSegment);
    }

    @Test
    void shouldRejectOldBusinessType()
    {
        LoanPricingWorkflow workflow = validWorkflow();
        workflow.setBusinessType("新客");

        ServiceException exception = assertThrows(ServiceException.class,
                () -> loanPricingWorkflowService.createLoanPricing(workflow));

        assertEquals("业务种类必须是：新增、存量新增、存量转贷之一", exception.getMessage());
    }

    @Test
    void shouldRequireHistoryRateForStockTransfer()
    {
        LoanPricingWorkflow workflow = validWorkflow();
        workflow.setBusinessType("存量转贷");

        ServiceException exception = assertThrows(ServiceException.class,
                () -> loanPricingWorkflowService.createLoanPricing(workflow));

        assertEquals("请选择历史贷款合同", exception.getMessage());
    }

    @Test
    void shouldRequireCouponRateForCertificatePledge()
    {
        LoanPricingWorkflow workflow = validWorkflow();
        workflow.setCustType("企业");
        workflow.setGuarType("质押");
        workflow.setCollType("存单质押");

        ServiceException exception = assertThrows(ServiceException.class,
                () -> loanPricingWorkflowService.createLoanPricing(workflow));

        assertEquals("存单票面利率不能为空", exception.getMessage());
    }

    @Test
    void shouldAllowPersonalMortgageFourthCategoryAndRejectOldFirstLine()
    {
        LoanPricingWorkflow allowedWorkflow = validWorkflow();
        allowedWorkflow.setCustType("个人");
        allowedWorkflow.setGuarType("抵押");
        allowedWorkflow.setCollType("四类");

        loanPricingWorkflowService.createLoanPricing(allowedWorkflow);

        LoanPricingWorkflow rejectedWorkflow = validWorkflow();
        rejectedWorkflow.setCustType("个人");
        rejectedWorkflow.setGuarType("抵押");
        rejectedWorkflow.setCollType("一线");

        ServiceException exception = assertThrows(ServiceException.class,
                () -> loanPricingWorkflowService.createLoanPricing(rejectedWorkflow));

        assertEquals("个人抵押抵质押类型必须是：一类、二类、三类、四类之一", exception.getMessage());
    }

    @Test
    void shouldAllowCorporateNewMortgageTypesAndRejectOldMortgageTypes()
    {
        LoanPricingWorkflow pollutionRightWorkflow = validWorkflow();
        pollutionRightWorkflow.setCustType("企业");
        pollutionRightWorkflow.setGuarType("抵押");
        pollutionRightWorkflow.setCollType("排污权抵押");

        loanPricingWorkflowService.createLoanPricing(pollutionRightWorkflow);

        LoanPricingWorkflow otherRealEstateWorkflow = validWorkflow();
        otherRealEstateWorkflow.setCustType("企业");
        otherRealEstateWorkflow.setGuarType("抵押");
        otherRealEstateWorkflow.setCollType("其他不动产抵押");

        loanPricingWorkflowService.createLoanPricing(otherRealEstateWorkflow);

        LoanPricingWorkflow oldFourthCategoryWorkflow = validWorkflow();
        oldFourthCategoryWorkflow.setCustType("企业");
        oldFourthCategoryWorkflow.setGuarType("抵押");
        oldFourthCategoryWorkflow.setCollType("四类");

        ServiceException fourthCategoryException = assertThrows(ServiceException.class,
                () -> loanPricingWorkflowService.createLoanPricing(oldFourthCategoryWorkflow));

        LoanPricingWorkflow oldOtherRealEstateWorkflow = validWorkflow();
        oldOtherRealEstateWorkflow.setCustType("企业");
        oldOtherRealEstateWorkflow.setGuarType("抵押");
        oldOtherRealEstateWorkflow.setCollType("设备等其他不动产抵押");

        ServiceException otherRealEstateException = assertThrows(ServiceException.class,
                () -> loanPricingWorkflowService.createLoanPricing(oldOtherRealEstateWorkflow));

        String expectedMessage = "企业抵押抵质押类型必须是：一类、二类、三类、排污权抵押、其他不动产抵押之一";
        assertEquals(expectedMessage, fourthCategoryException.getMessage());
        assertEquals(expectedMessage, otherRealEstateException.getMessage());
    }

    @Test
    void shouldKeepCorporatePledgeTypesUnchanged()
    {
        LoanPricingWorkflow pledgeWorkflow = validWorkflow();
        pledgeWorkflow.setCustType("企业");
        pledgeWorkflow.setGuarType("质押");
        pledgeWorkflow.setCollType("股权质押");

        loanPricingWorkflowService.createLoanPricing(pledgeWorkflow);

        LoanPricingWorkflow rejectedWorkflow = validWorkflow();
        rejectedWorkflow.setCustType("企业");
        rejectedWorkflow.setGuarType("质押");
        rejectedWorkflow.setCollType("其他");

        ServiceException exception = assertThrows(ServiceException.class,
                () -> loanPricingWorkflowService.createLoanPricing(rejectedWorkflow));

        assertEquals("企业质押抵质押类型必须是：存单质押、股权质押、其他质押之一", exception.getMessage());
    }

    @Test
    void shouldCreatePersonalWorkflowWithCouponRateAndWithoutRemovedFields()
    {
        PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
        dto.setCustIsn("CUST001");
        dto.setGuarType("质押");
        dto.setApplyAmt("100000");
        dto.setBusinessType("新增");
        dto.setLoanTerm("3");
        dto.setCollType("存单质押");
        dto.setCouponRate("2.15");

        loanPricingWorkflowService.createPersonalLoanPricing(dto);

        verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) ->
                Objects.equals("个人", entity.getCustType())
                        && Objects.equals("2.15", entity.getCouponRate())
                        && Objects.isNull(entity.getLoanPurpose())
                        && Objects.isNull(entity.getBizProof())));
    }

    @Test
    void shouldCreateCorporateWorkflowWithCouponRateAndBusinessType()
    {
        CorporateLoanPricingCreateDTO dto = new CorporateLoanPricingCreateDTO();
        dto.setCustIsn("CORP001");
        dto.setGuarType("质押");
        dto.setApplyAmt("1000000");
        dto.setBusinessType("新增");
        dto.setLoanTerm("3");
        dto.setCollType("存单质押");
        dto.setCouponRate("2.35");
        dto.setResCover("1");

        loanPricingWorkflowService.createCorporateLoanPricing(dto);

        verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) ->
                Objects.equals("企业", entity.getCustType())
                        && Objects.equals("新增", entity.getBusinessType())
                        && Objects.equals("1", entity.getResCover())
                        && Objects.equals("2.35", entity.getCouponRate())));
    }

    @Test
    void shouldReturnEditableWorkflowWithPlainSensitiveFieldsForCreator()
    {
        LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin");
        workflow.setCustName("张三");
        workflow.setIdNum("110101199001011234");

        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);

        LoanPricingWorkflow result = loanPricingWorkflowService.selectEditableLoanPricingBySerialNum("P20260525001");

        assertEquals("张三", result.getCustName());
        assertEquals("110101199001011234", result.getIdNum());
        verify(sensitiveFieldCryptoService, never()).decrypt(any());
    }

    @Test
    void shouldRejectEditableWorkflowWhenCurrentUserIsNotCreator()
    {
        LoanPricingWorkflow workflow = editableWorkflow("个人", "其他用户-8920001");
        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);

        ServiceException exception = assertThrows(ServiceException.class,
                () -> loanPricingWorkflowService.selectEditableLoanPricingBySerialNum("P20260525001"));

        assertEquals("只有创建者可以编辑该流程", exception.getMessage());
    }

    @Test
    void shouldUpdatePersonalWorkflowAndReinvokeModelForCreator()
    {
        LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin");
        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);

        PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
        dto.setCustIsn("CUST001");
        dto.setCustName("张三");
        dto.setIdType("身份证");
        dto.setIdNum("110101199001019999");
        dto.setGuarType("信用");
        dto.setApplyAmt("200000");
        dto.setBusinessType("新增");
        dto.setLoanTerm("3");
        dto.setLoanLoop("1");

        loanPricingWorkflowService.updatePersonalLoanPricing("P20260525001", dto);

        ArgumentCaptor<LambdaUpdateWrapper> updateWrapperCaptor = ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
        verify(loanPricingWorkflowMapper).update(any(), updateWrapperCaptor.capture());
        String sqlSet = updateWrapperCaptor.getValue().getSqlSet();
        assertTrue(sqlSet.contains("apply_amt"));
        assertTrue(sqlSet.contains("cust_name"));
        assertTrue(sqlSet.contains("id_num"));
        assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("张三"));
        assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("110101199001019999"));
        verify(sensitiveFieldCryptoService, never()).encrypt(any());
        verify(loanPricingModelService).reinvokeModelAndOverwrite(101L);
    }

    @Test
    void shouldUpdateCorporateWorkflowAndReinvokeModelForCreator()
    {
        LoanPricingWorkflow workflow = editableWorkflow("企业", "若依-admin");
        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);

        CorporateLoanPricingCreateDTO dto = new CorporateLoanPricingCreateDTO();
        dto.setCustIsn("CORP001");
        dto.setCustName("测试科技有限公司");
        dto.setIdType("统一社会信用代码");
        dto.setIdNum("91110000100000000X");
        dto.setGuarType("质押");
        dto.setApplyAmt("300000");
        dto.setBusinessType("新增");
        dto.setLoanTerm("5");
        dto.setCollType("存单质押");
        dto.setCollThirdParty("1");
        dto.setCouponRate("2.35");
        dto.setResCover("1");
        dto.setIsGreenLoan("1");
        dto.setIsTradeBuildEnt("0");

        loanPricingWorkflowService.updateCorporateLoanPricing("C20260525001", dto);

        ArgumentCaptor<LambdaUpdateWrapper> updateWrapperCaptor = ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
        verify(loanPricingWorkflowMapper).update(any(), updateWrapperCaptor.capture());
        String sqlSet = updateWrapperCaptor.getValue().getSqlSet();
        assertTrue(sqlSet.contains("res_cover"), sqlSet);
        assertTrue(sqlSet.contains("is_green_loan"), sqlSet);
        assertTrue(sqlSet.contains("is_trade_construction"), sqlSet);
        assertTrue(sqlSet.contains("coll_third_party"), sqlSet);
        assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("测试科技有限公司"));
        assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("91110000100000000X"));
        verify(sensitiveFieldCryptoService, never()).encrypt(any());
        verify(loanPricingModelService).reinvokeModelAndOverwrite(101L);
    }

    @Test
    void shouldRejectUpdateWhenCustomerTypeDoesNotMatch()
    {
        LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin");
        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);

        CorporateLoanPricingCreateDTO dto = new CorporateLoanPricingCreateDTO();
        dto.setCustIsn("CORP001");
        dto.setGuarType("信用");
        dto.setApplyAmt("300000");
        dto.setBusinessType("新增");
        dto.setLoanTerm("5");

        ServiceException exception = assertThrows(ServiceException.class,
                () -> loanPricingWorkflowService.updateCorporateLoanPricing("P20260525001", dto));

        assertEquals("客户类型不匹配，不能编辑该流程", exception.getMessage());
    }

    @Test
    void shouldRejectUpdateWhenCurrentUserIsNotCreator()
    {
        LoanPricingWorkflow workflow = editableWorkflow("个人", "其他用户-8920001");
        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);

        PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
        dto.setCustIsn("CUST001");
        dto.setGuarType("信用");
        dto.setApplyAmt("200000");
        dto.setBusinessType("新增");
        dto.setLoanTerm("3");

        ServiceException exception = assertThrows(ServiceException.class,
                () -> loanPricingWorkflowService.updatePersonalLoanPricing("P20260525001", dto));

        assertEquals("只有创建者可以编辑该流程", exception.getMessage());
    }

    @Test
    void shouldUseRetailModelOutputFinalCalculateRateForWorkflowDetail()
    {
        LoanPricingWorkflow workflow = new LoanPricingWorkflow();
        workflow.setSerialNum("P20260328001");
        workflow.setCustType("个人");
        workflow.setModelOutputId(11L);
        workflow.setLoanRate("4.35");

        ModelRetailOutputFields retailOutputFields = new ModelRetailOutputFields();
        retailOutputFields.setCalculateRate("6.15");
        retailOutputFields.setFinalCalculateRate("6.05");

        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
        when(modelRetailOutputFieldsMapper.selectById(11L)).thenReturn(retailOutputFields);

        LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");

        assertEquals("6.05", result.getLoanPricingWorkflow().getLoanRate());
        assertEquals("6.15", result.getModelRetailOutputFields().getCalculateRate());
        assertEquals("6.05", result.getModelRetailOutputFields().getFinalCalculateRate());
    }

    @Test
    void shouldReturnPlainCustNameAndIdNumWhenReturningWorkflowDetail()
    {
        LoanPricingWorkflow workflow = new LoanPricingWorkflow();
        workflow.setSerialNum("P20260328001");
        workflow.setCustType("个人");
        workflow.setCustName("张三");
        workflow.setIdNum("110101199001011234");

        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);

        LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");

        assertEquals("张三", result.getLoanPricingWorkflow().getCustName());
        assertEquals("110101199001011234", result.getLoanPricingWorkflow().getIdNum());
        verify(sensitiveFieldCryptoService, never()).decrypt(any());
    }

    @Test
    void shouldReturnPlainCustNameAndIdNumInRetailModelOutputBasicInfo()
    {
        LoanPricingWorkflow workflow = new LoanPricingWorkflow();
        workflow.setSerialNum("P20260328001");
        workflow.setCustType("个人");
        workflow.setCustName("张三");
        workflow.setIdNum("110101199001011234");
        workflow.setModelOutputId(11L);

        ModelRetailOutputFields retailOutputFields = new ModelRetailOutputFields();
        retailOutputFields.setCustName("张三");
        retailOutputFields.setIdNum("110101199001011234");
        retailOutputFields.setCalculateRate("6.15");

        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
        when(modelRetailOutputFieldsMapper.selectById(11L)).thenReturn(retailOutputFields);

        LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");

        assertEquals("张三", result.getModelRetailOutputFields().getCustName());
        assertEquals("110101199001011234", result.getModelRetailOutputFields().getIdNum());
    }

    @Test
    void shouldUseCorporateModelOutputCalculateRateForWorkflowDetail()
    {
        LoanPricingWorkflow workflow = new LoanPricingWorkflow();
        workflow.setSerialNum("C20260328001");
        workflow.setCustType("企业");
        workflow.setModelOutputId(22L);
        workflow.setLoanRate("3.80");

        ModelCorpOutputFields corpOutputFields = new ModelCorpOutputFields();
        corpOutputFields.setCalculateRate("3.932");
        corpOutputFields.setFinalCalculateRate("3.652");

        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
        when(modelCorpOutputFieldsMapper.selectById(22L)).thenReturn(corpOutputFields);

        LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001");

        assertEquals("3.652", result.getLoanPricingWorkflow().getLoanRate());
        assertEquals("3.932", result.getModelCorpOutputFields().getCalculateRate());
        assertEquals("3.652", result.getModelCorpOutputFields().getFinalCalculateRate());
    }

    @Test
    void shouldReturnPlainCustNameAndIdNumInCorporateModelOutputBasicInfo()
    {
        LoanPricingWorkflow workflow = new LoanPricingWorkflow();
        workflow.setSerialNum("C20260328001");
        workflow.setCustType("企业");
        workflow.setCustName("测试科技有限公司");
        workflow.setIdNum("91110000100000000X");
        workflow.setModelOutputId(22L);

        ModelCorpOutputFields corpOutputFields = new ModelCorpOutputFields();
        corpOutputFields.setCustName("测试科技有限公司");
        corpOutputFields.setIdNum("91110000100000000X");
        corpOutputFields.setCalculateRate("3.932");

        when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
        when(modelCorpOutputFieldsMapper.selectById(22L)).thenReturn(corpOutputFields);

        LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001");

        assertEquals("测试科技有限公司", result.getModelCorpOutputFields().getCustName());
        assertEquals("91110000100000000X", result.getModelCorpOutputFields().getIdNum());
    }

    @Test
    void shouldDecryptEncryptedWorkflowFieldsAndSkipPlainFields()
    {
        LoanPricingWorkflow encrypted = new LoanPricingWorkflow();
        encrypted.setId(1L);
        encrypted.setCustName("cipher-name");
        encrypted.setIdNum("cipher-id");
        LoanPricingWorkflow plain = new LoanPricingWorkflow();
        plain.setId(2L);
        plain.setCustName("张三");
        plain.setIdNum("");

        when(loanPricingWorkflowMapper.selectList(any())).thenReturn(List.of(encrypted, plain));
        when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
        when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
        when(sensitiveFieldCryptoService.decrypt("张三")).thenThrow(new ServiceException("贷款定价敏感字段解密失败"));

        Map<String, Long> result = loanPricingWorkflowService.decryptWorkflowSensitiveFields();

        assertEquals(2L, result.get("processed"));
        assertEquals(1L, result.get("updated"));
        assertEquals(1L, result.get("skipped"));
        assertEquals(0L, result.get("failed"));
        verify(loanPricingWorkflowMapper).updateSensitiveFieldsById(1L, "张三", "110101199001011234");
        verify(loanPricingWorkflowMapper, never()).updateById(any(LoanPricingWorkflow.class));
    }

    @Test
    void shouldUpdateOnlySuccessfullyDecryptedWorkflowField()
    {
        LoanPricingWorkflow workflow = new LoanPricingWorkflow();
        workflow.setId(3L);
        workflow.setCustName("cipher-name");
        workflow.setIdNum("plain-id");

        when(loanPricingWorkflowMapper.selectList(any())).thenReturn(List.of(workflow));
        when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
        when(sensitiveFieldCryptoService.decrypt("plain-id")).thenThrow(new ServiceException("贷款定价敏感字段解密失败"));

        Map<String, Long> result = loanPricingWorkflowService.decryptWorkflowSensitiveFields();

        assertEquals(1L, result.get("processed"));
        assertEquals(1L, result.get("updated"));
        assertEquals(0L, result.get("skipped"));
        assertEquals(0L, result.get("failed"));
        verify(loanPricingWorkflowMapper).updateSensitiveFieldsById(3L, "张三", null);
        verify(loanPricingWorkflowMapper, never()).updateById(any(LoanPricingWorkflow.class));
    }

    @Test
    void shouldCountDatabaseUpdateExceptionAsMigrationFailure()
    {
        LoanPricingWorkflow workflow = new LoanPricingWorkflow();
        workflow.setId(4L);
        workflow.setCustName("cipher-name");

        when(loanPricingWorkflowMapper.selectList(any())).thenReturn(List.of(workflow));
        when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
        when(loanPricingWorkflowMapper.updateSensitiveFieldsById(4L, "张三", null)).thenThrow(new RuntimeException("db error"));

        Map<String, Long> result = loanPricingWorkflowService.decryptWorkflowSensitiveFields();

        assertEquals(1L, result.get("processed"));
        assertEquals(0L, result.get("updated"));
        assertEquals(0L, result.get("skipped"));
        assertEquals(1L, result.get("failed"));
    }

    private LoanPricingWorkflow validWorkflow()
    {
        LoanPricingWorkflow workflow = new LoanPricingWorkflow();
        workflow.setCustIsn("CUST001");
        workflow.setCustType("个人");
        workflow.setGuarType("信用");
        workflow.setApplyAmt("100000");
        workflow.setBusinessType("新增");
        return workflow;
    }

    private LoanPricingWorkflow editableWorkflow(String custType, String createBy)
    {
        LoanPricingWorkflow workflow = new LoanPricingWorkflow();
        workflow.setId(101L);
        workflow.setSerialNum("个人".equals(custType) ? "P20260525001" : "C20260525001");
        workflow.setModelOutputId(201L);
        workflow.setCustIsn("CUST001");
        workflow.setCustType(custType);
        workflow.setCustName("张三");
        workflow.setIdType("身份证");
        workflow.setIdNum("110101199001011234");
        workflow.setGuarType("信用");
        workflow.setApplyAmt("100000");
        workflow.setBusinessType("新增");
        workflow.setLoanTerm("3");
        workflow.setCreateBy(createBy);
        return workflow;
    }

    private Page<LoanPricingWorkflowListVO> emptyPageResult()
    {
        Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
        pageResult.setRecords(Collections.emptyList());
        return pageResult;
    }

    private void setLoginUser(Long userId, String username, String nickName, SysRole... roles)
    {
        setLoginUser(userId, 100L, username, nickName, roles);
    }

    private void setLoginUser(Long userId, Long deptId, String username, String nickName, SysRole... roles)
    {
        SysUser user = new SysUser();
        user.setUserId(userId);
        user.setDeptId(deptId);
        user.setUserName(username);
        user.setNickName(nickName);
        user.setRoles(java.util.Arrays.asList(roles));
        LoginUser loginUser = new LoginUser(userId, deptId, user, Collections.emptySet());
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    private SysRole role(Long roleId, String roleName, String roleKey)
    {
        SysRole role = new SysRole(roleId);
        role.setRoleName(roleName);
        role.setRoleKey(roleKey);
        role.setStatus(UserConstants.ROLE_NORMAL);
        return role;
    }
}
