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

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import com.ruoyi.ccdi.project.domain.CcdiModelParam;
import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagRule;
import com.ruoyi.ccdi.project.domain.vo.BankTagRuleExecutionConfig;
import com.ruoyi.ccdi.project.mapper.CcdiModelParamMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
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 org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class BankTagRuleConfigResolverTest {

    @InjectMocks
    private BankTagRuleConfigResolver resolver;

    @Mock
    private CcdiProjectMapper projectMapper;

    @Mock
    private CcdiModelParamMapper modelParamMapper;

    @Test
    void resolve_shouldReadEffectiveProjectParamsForThresholdRules() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("default");
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(modelParamMapper.selectByProjectAndModel(0L, "LARGE_TRANSACTION")).thenReturn(List.of(
            buildParam("SINGLE_TRANSACTION_AMOUNT", "1111")
        ));

        CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
        ruleMeta.setModelCode("LARGE_TRANSACTION");
        ruleMeta.setRuleCode("SINGLE_LARGE_INCOME");
        ruleMeta.setIndicatorCode("SINGLE_TRANSACTION_AMOUNT");

        BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);

        assertEquals("1111", config.getThresholdValue("SINGLE_TRANSACTION_AMOUNT"));
    }

    @Test
    void resolve_shouldLogThresholdSourceAndMissingParams() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("default");
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(modelParamMapper.selectByProjectAndModel(0L, "LARGE_TRANSACTION")).thenReturn(List.of(
            buildParam("LARGE_CASH_DEPOSIT", "50000")
        ));

        CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
        ruleMeta.setModelCode("LARGE_TRANSACTION");
        ruleMeta.setRuleCode("FREQUENT_CASH_DEPOSIT");

        Logger logger = (Logger) LoggerFactory.getLogger(BankTagRuleConfigResolver.class);
        ListAppender<ILoggingEvent> logAppender = new ListAppender<>();
        logAppender.start();
        logger.addAppender(logAppender);

        try {
            resolver.resolve(40L, ruleMeta);

            assertTrue(logAppender.list.stream().map(ILoggingEvent::getFormattedMessage)
                .anyMatch(message -> message.contains("解析规则参数")
                    && message.contains("projectId=40")
                    && message.contains("effectiveProjectId=0")
                    && message.contains("FREQUENT_CASH_DEPOSIT")));
            assertTrue(logAppender.list.stream().map(ILoggingEvent::getFormattedMessage)
                .anyMatch(message -> message.contains("规则参数缺失")
                    && message.contains("projectId=40")
                    && message.contains("FREQUENT_CASH_DEPOSIT")));
        } finally {
            logger.detachAppender(logAppender);
        }
    }

    @Test
    void resolve_shouldReturnEmptyThresholdsForPlaceholderRulesWithoutIndicatorCode() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("default");
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(modelParamMapper.selectByProjectAndModel(0L, "ABNORMAL_TRANSACTION")).thenReturn(List.of(
            buildParam("IGNORED_PARAM", "999")
        ));

        CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
        ruleMeta.setModelCode("ABNORMAL_TRANSACTION");
        ruleMeta.setRuleCode("ABNORMAL_CUSTOMER_TRANSACTION");
        ruleMeta.setIndicatorCode(null);

        BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);

        assertTrue(config.getThresholdValues().isEmpty());
    }

    @Test
    void resolve_shouldReadUppercaseAnnualTurnoverParamCode() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("default");
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(modelParamMapper.selectByProjectAndModel(0L, "LARGE_TRANSACTION")).thenReturn(List.of(
            buildParam("ANNUAL_TURNOVER", "8888")
        ));

        CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
        ruleMeta.setModelCode("LARGE_TRANSACTION");
        ruleMeta.setRuleCode("ANNUAL_TURNOVER");
        ruleMeta.setIndicatorCode("ANNUAL_TURNOVER");

        BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);

        assertEquals("8888", config.getThresholdValue("ANNUAL_TURNOVER"));
    }

    @Test
    void resolve_shouldUseLargeTransactionParamsForExternalLargeTransactionRule() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("default");
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(modelParamMapper.selectByProjectAndModel(0L, "LARGE_TRANSACTION")).thenReturn(List.of(
            buildParam("LARGE_TRANSACTION", "FREQUENT_TRANSFER", "100000")
        ));

        CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
        ruleMeta.setModelCode("EXTERNAL_LARGE_TRANSACTION");
        ruleMeta.setRuleCode("EXTERNAL_SINGLE_LARGE_AMOUNT");
        ruleMeta.setIndicatorCode("FREQUENT_TRANSFER");

        BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);

        assertEquals("100000", config.getThresholdValue("FREQUENT_TRANSFER"));
    }

    @Test
    void resolve_shouldUseOriginalModelParamsForExternalObjectRules() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("default");
        when(projectMapper.selectById(40L)).thenReturn(project);
        when(modelParamMapper.selectByProjectAndModel(0L, "LARGE_TRANSACTION")).thenReturn(List.of(
            buildParam("LARGE_TRANSACTION", "CUMULATIVE_TRANSACTION_AMOUNT", "500000"),
            buildParam("LARGE_TRANSACTION", "ANNUAL_TURNOVER", "800000")
        ));
        when(modelParamMapper.selectByProjectAndModel(0L, "SUSPICIOUS_GAMBLING")).thenReturn(List.of(
            buildParam("SUSPICIOUS_GAMBLING", "MULTI_PARTY_AMT_MIN", "500"),
            buildParam("SUSPICIOUS_GAMBLING", "MULTI_PARTY_AMT_MAX", "5000")
        ));

        assertRuleThresholds("EXTERNAL_LARGE_TRANSACTION", "EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT",
            Map.of("CUMULATIVE_TRANSACTION_AMOUNT", "500000"));
        assertRuleThresholds("EXTERNAL_LARGE_TRANSACTION", "EXTERNAL_ANNUAL_TURNOVER",
            Map.of("ANNUAL_TURNOVER", "800000"));
        assertRuleThresholds("EXTERNAL_SUSPICIOUS_GAMBLING", "EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER",
            Map.of("MULTI_PARTY_AMT_MIN", "500", "MULTI_PARTY_AMT_MAX", "5000"));
    }

    @Test
    void resolve_shouldMapPhaseOneThresholdRulesToUppercaseParamCodes() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("default");
        when(projectMapper.selectById(40L)).thenReturn(project);

        assertSingleThresholdRule("SUSPICIOUS_FOREIGN_EXCHANGE", "FOREX_BUY_AMT",
            "SINGLE_PURCHASE_AMOUNT", "50000");
        assertSingleThresholdRule("SUSPICIOUS_FOREIGN_EXCHANGE", "FOREX_SELL_AMT",
            "SINGLE_SETTLEMENT_AMOUNT", "60000");
        assertSingleThresholdRule("ABNORMAL_BEHAVIOR", "WITHDRAW_CNT",
            "WITHDRAW_CNT", "3");
        assertSingleThresholdRule("ABNORMAL_BEHAVIOR", "STOCK_TFR_LARGE",
            "STOCK_TFR_LARGE", "1000000");
        assertSingleThresholdRule("ABNORMAL_BEHAVIOR", "LARGE_STOCK_TRADING",
            "STOCK_TFR_LARGE", "1000000");
    }

    @Test
    void resolve_shouldKeepEmptyThresholdsForPhaseOneRulesWithoutParams() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("default");
        when(projectMapper.selectById(40L)).thenReturn(project);

        assertRuleHasNoThresholds("SUSPICIOUS_GAMBLING", "GAMBLING_SENSITIVE_KEYWORD");
        assertRuleHasNoThresholds("SUSPICIOUS_RELATION", "SPECIAL_AMOUNT_TRANSACTION");
        assertRuleHasNoThresholds("SUSPICIOUS_PART_TIME", "SUSPICIOUS_INCOME_KEYWORD");
        assertRuleHasNoThresholds("SUSPICIOUS_PURCHASE", "LARGE_PURCHASE_TRANSACTION");
    }

    @Test
    void resolve_shouldSupportPhaseTwoThresholdRulesAndKeepNoParamRulesEmpty() {
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setConfigType("default");
        when(projectMapper.selectById(40L)).thenReturn(project);

        when(modelParamMapper.selectByProjectAndModel(0L, "SUSPICIOUS_GAMBLING")).thenReturn(List.of(
            buildParam("SUSPICIOUS_GAMBLING", "MULTI_PARTY_AMT_MIN", "500"),
            buildParam("SUSPICIOUS_GAMBLING", "MULTI_PARTY_AMT_MAX", "5000")
        ));
        when(modelParamMapper.selectByProjectAndModel(0L, "SUSPICIOUS_PART_TIME")).thenReturn(List.of(
            buildParam("SUSPICIOUS_PART_TIME", "MONTHLY_FIXED_INCOME", "5000"),
            buildParam("SUSPICIOUS_PART_TIME", "FIXED_COUNTERPARTY_TRANSFER_MIN", "3000"),
            buildParam("SUSPICIOUS_PART_TIME", "FIXED_COUNTERPARTY_TRANSFER_MAX", "15000")
        ));
        when(modelParamMapper.selectByProjectAndModel(0L, "ABNORMAL_TRANSACTION")).thenReturn(List.of(
            buildParam("ABNORMAL_TRANSACTION", "IGNORED_PARAM", "999")
        ));
        when(modelParamMapper.selectByProjectAndModel(0L, "SUSPICIOUS_PURCHASE")).thenReturn(List.of(
            buildParam("SUSPICIOUS_PURCHASE", "IGNORED_PARAM", "999")
        ));
        when(modelParamMapper.selectByProjectAndModel(0L, "SUSPICIOUS_PROPERTY")).thenReturn(List.of(
            buildParam("SUSPICIOUS_PROPERTY", "IGNORED_PARAM", "999")
        ));
        when(modelParamMapper.selectByProjectAndModel(0L, "ABNORMAL_BEHAVIOR")).thenReturn(List.of(
            buildParam("ABNORMAL_BEHAVIOR", "IGNORED_PARAM", "999")
        ));

        assertAll(
            () -> assertRuleThresholds("SUSPICIOUS_GAMBLING", "MULTI_PARTY_GAMBLING_TRANSFER",
                Map.of("MULTI_PARTY_AMT_MIN", "500", "MULTI_PARTY_AMT_MAX", "5000")),
            () -> assertRuleThresholds("SUSPICIOUS_PART_TIME", "MONTHLY_FIXED_INCOME",
                Map.of("MONTHLY_FIXED_INCOME", "5000")),
            () -> assertRuleThresholds("SUSPICIOUS_PART_TIME", "FIXED_COUNTERPARTY_TRANSFER",
                Map.of("FIXED_COUNTERPARTY_TRANSFER_MIN", "3000", "FIXED_COUNTERPARTY_TRANSFER_MAX", "15000")),
            () -> assertRuleHasNoThresholds("ABNORMAL_TRANSACTION", "LOW_INCOME_RELATIVE_LARGE_TRANSACTION"),
            () -> assertRuleHasNoThresholds("SUSPICIOUS_PURCHASE", "SUPPLIER_CONCENTRATION"),
            () -> assertRuleHasNoThresholds("SUSPICIOUS_PROPERTY", "HOUSE_REGISTRATION_MISMATCH"),
            () -> assertRuleHasNoThresholds("SUSPICIOUS_PROPERTY", "PROPERTY_FEE_REGISTRATION_MISMATCH"),
            () -> assertRuleHasNoThresholds("SUSPICIOUS_PROPERTY", "TAX_ASSET_REGISTRATION_MISMATCH"),
            () -> assertRuleHasNoThresholds("ABNORMAL_BEHAVIOR", "SALARY_QUICK_TRANSFER"),
            () -> assertRuleHasNoThresholds("ABNORMAL_BEHAVIOR", "SALARY_UNUSED")
        );
    }

    private CcdiModelParam buildParam(String paramCode, String paramValue) {
        CcdiModelParam param = new CcdiModelParam();
        param.setProjectId(0L);
        param.setModelCode("LARGE_TRANSACTION");
        param.setParamCode(paramCode);
        param.setParamValue(paramValue);
        return param;
    }

    private void assertSingleThresholdRule(String modelCode, String ruleCode, String paramCode, String paramValue) {
        when(modelParamMapper.selectByProjectAndModel(0L, modelCode)).thenReturn(List.of(
            buildParam(modelCode, paramCode, paramValue)
        ));

        CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
        ruleMeta.setModelCode(modelCode);
        ruleMeta.setRuleCode(ruleCode);
        ruleMeta.setIndicatorCode(paramCode);

        BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);

        assertEquals(Map.of(paramCode, paramValue), config.getThresholdValues());
    }

    private void assertRuleHasNoThresholds(String modelCode, String ruleCode) {
        when(modelParamMapper.selectByProjectAndModel(0L, modelCode)).thenReturn(List.of(
            buildParam(modelCode, "IGNORED_PARAM", "999")
        ));

        CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
        ruleMeta.setModelCode(modelCode);
        ruleMeta.setRuleCode(ruleCode);

        BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);

        assertTrue(config.getThresholdValues().isEmpty());
    }

    private void assertRuleThresholds(String modelCode, String ruleCode, Map<String, String> expectedThresholds) {
        CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
        ruleMeta.setModelCode(modelCode);
        ruleMeta.setRuleCode(ruleCode);

        BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);

        assertEquals(expectedThresholds, config.getThresholdValues());
    }

    private CcdiModelParam buildParam(String modelCode, String paramCode, String paramValue) {
        CcdiModelParam param = new CcdiModelParam();
        param.setProjectId(0L);
        param.setModelCode(modelCode);
        param.setParamCode(paramCode);
        param.setParamValue(paramValue);
        return param;
    }
}
