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

import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants;
import com.ruoyi.ccdi.project.domain.dto.CcdiBankTagRebuildDTO;
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagResult;
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagRule;
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagTask;
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
import com.ruoyi.ccdi.project.domain.vo.BankTagObjectHitVO;
import com.ruoyi.ccdi.project.domain.vo.BankTagRuleExecutionConfig;
import com.ruoyi.ccdi.project.domain.vo.BankTagStatementHitVO;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagAnalysisMapper;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagRuleMapper;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper;
import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import lombok.extern.slf4j.Slf4j;

/**
 * 流水标签服务实现
 */
@Slf4j
@Service
public class CcdiBankTagServiceImpl implements ICcdiBankTagService {

    private static final String STATUS_RUNNING = "RUNNING";
    private static final String STATUS_SUCCESS = "SUCCESS";
    private static final String STATUS_FAILED = "FAILED";
    private static final String TASK_ERROR_MESSAGE_FALLBACK = "任务失败，详细异常请查看后端日志";
    private static final String RESULT_TYPE_STATEMENT = "STATEMENT";
    private static final String OBJECT_TYPE_STAFF_ID_CARD = "STAFF_ID_CARD";

    @Resource
    private CcdiBankTagRuleMapper ruleMapper;

    @Resource
    private CcdiBankTagResultMapper resultMapper;

    @Resource
    private CcdiBankTagTaskMapper taskMapper;

    @Resource
    private CcdiBankTagAnalysisMapper analysisMapper;

    @Resource
    private BankTagRuleConfigResolver configResolver;

    @Resource
    private ICcdiProjectService projectService;

    @Resource
    private ICcdiProjectOverviewService projectOverviewService;

    @Resource
    @Qualifier("tagRuleExecutor")
    private Executor tagRuleExecutor;

    @Lazy
    @Resource
    private ProjectBankTagRebuildCoordinator coordinator;

    @Override
    public String submitRebuild(CcdiBankTagRebuildDTO dto, String operator) {
        coordinator.submitManual(dto.getProjectId(), dto.getModelCode(), operator);
        return "标签重算任务已提交";
    }

    /**
     * 提交自动重算
     *
     * @param projectId 项目ID
     * @param triggerType 触发方式
     */
    public void submitAutoRebuild(Long projectId, TriggerType triggerType) {
        coordinator.submitAuto(projectId, triggerType);
    }

    /**
     * 执行项目标签重算
     *
     * @param projectId 项目ID
     * @param modelCode 模型编码
     * @param operator 操作人
     * @param triggerType 触发方式
     */
    public Long rebuildProject(Long projectId, String modelCode, String operator, TriggerType triggerType) {
        long taskStartTime = System.currentTimeMillis();
        projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.TAGGING, operator);
        CcdiBankTagTask task = buildRunningTask(projectId, modelCode, operator, triggerType);
        taskMapper.insertTask(task);
        log.info("【流水标签】任务创建成功: taskId={}, projectId={}, modelCode={}, triggerType={}, operator={}",
            task.getId(), projectId, modelCode, triggerType, operator);

        try {
            List<CcdiBankTagRule> rules = ruleMapper.selectEnabledRules(modelCode);
            log.info("【流水标签】加载启用规则完成: taskId={}, projectId={}, modelCode={}, ruleCount={}",
                task.getId(), projectId, modelCode, rules.size());
            log.info("【流水标签】开始清理历史结果: taskId={}, projectId={}, modelCode={}",
                task.getId(), projectId, modelCode);
            resultMapper.deleteByProjectAndModel(projectId, modelCode);

            List<CompletableFuture<List<CcdiBankTagResult>>> futures = rules.stream()
                .map(rule -> CompletableFuture.supplyAsync(
                    () -> executeRule(task.getId(), projectId, rule, operator),
                    tagRuleExecutor
                ))
                .toList();

            List<CcdiBankTagResult> allResults = futures.stream()
                .map(CompletableFuture::join)
                .flatMap(List::stream)
                .toList();

            if (!allResults.isEmpty()) {
                log.info("【流水标签】批量写入标签结果: taskId={}, projectId={}, resultCount={}",
                    task.getId(), projectId, allResults.size());
                resultMapper.insertBatch(allResults);
            }

            projectOverviewService.refreshOverviewEmployeeResults(projectId, operator);

            task.setStatus(STATUS_SUCCESS);
            task.setSuccessRuleCount(rules.size());
            task.setFailedRuleCount(0);
            task.setHitCount(allResults.size());
            task.setEndTime(new Date());
            task.setNeedRerun(null);
            task.setUpdateBy(operator);
            task.setUpdateTime(new Date());
            taskMapper.updateTask(task);
            projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.COMPLETED, operator);
            log.info("【流水标签】任务执行成功: taskId={}, projectId={}, modelCode={}, triggerType={}, ruleCount={}, hitCount={}, costMs={}",
                task.getId(), projectId, modelCode, triggerType, rules.size(), allResults.size(),
                System.currentTimeMillis() - taskStartTime);
            return task.getId();
        } catch (Exception ex) {
            task.setStatus(STATUS_FAILED);
            task.setEndTime(new Date());
            task.setNeedRerun(null);
            task.setUpdateBy(operator);
            task.setUpdateTime(new Date());
            updateFailedTaskSafely(task, ex);
            projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.TAG_FAILED, operator);
            log.error("【流水标签】任务执行失败: taskId={}, projectId={}, modelCode={}, triggerType={}, error={}",
                task.getId(), projectId, modelCode, triggerType, ex.getMessage(), ex);
            throw ex;
        }
    }

    private CcdiBankTagTask buildRunningTask(Long projectId, String modelCode, String operator, TriggerType triggerType) {
        Date now = new Date();
        CcdiBankTagTask task = new CcdiBankTagTask();
        task.setProjectId(projectId);
        task.setModelCode(modelCode);
        task.setTriggerType(triggerType.name());
        task.setStatus(STATUS_RUNNING);
        task.setNeedRerun(0);
        task.setSuccessRuleCount(0);
        task.setFailedRuleCount(0);
        task.setHitCount(0);
        task.setStartTime(now);
        task.setCreateBy(operator);
        task.setCreateTime(now);
        task.setUpdateBy(operator);
        task.setUpdateTime(now);
        return task;
    }

    private List<CcdiBankTagResult> executeRule(Long taskId, Long projectId, CcdiBankTagRule rule, String operator) {
        long startTime = System.currentTimeMillis();
        log.info("【流水标签】规则开始执行: taskId={}, projectId={}, ruleCode={}, resultType={}",
            taskId, projectId, rule.getRuleCode(), rule.getResultType());
        BankTagRuleExecutionConfig config = configResolver.resolve(projectId, rule);
        log.debug("【流水标签】规则执行参数: taskId={}, ruleCode={}, thresholds={}",
            taskId, rule.getRuleCode(), config.getThresholdValues());
        if (RESULT_TYPE_STATEMENT.equals(rule.getResultType())) {
            List<BankTagStatementHitVO> hits = executeStatementRule(projectId, rule, config);
            List<CcdiBankTagResult> results = buildStatementResults(projectId, rule, hits, operator);
            logRuleCompletion(taskId, projectId, rule.getRuleCode(), results.size(), startTime);
            return results;
        }

        List<BankTagObjectHitVO> hits = executeObjectRule(projectId, rule, config);
        List<CcdiBankTagResult> results = buildObjectResults(projectId, rule, hits, operator);
        logRuleCompletion(taskId, projectId, rule.getRuleCode(), results.size(), startTime);
        return results;
    }

    private void logRuleCompletion(Long taskId, Long projectId, String ruleCode, int hitCount, long startTime) {
        long costMs = System.currentTimeMillis() - startTime;
        if (hitCount == 0) {
            log.warn("【流水标签】规则无命中: taskId={}, projectId={}, ruleCode={}, costMs={}",
                taskId, projectId, ruleCode, costMs);
            return;
        }
        log.info("【流水标签】规则执行完成: taskId={}, projectId={}, ruleCode={}, hitCount={}, costMs={}",
            taskId, projectId, ruleCode, hitCount, costMs);
    }

    private List<BankTagStatementHitVO> executeStatementRule(Long projectId,
                                                             CcdiBankTagRule rule,
                                                             BankTagRuleExecutionConfig config) {
        return switch (rule.getRuleCode()) {
            case "HOUSE_OR_CAR_EXPENSE" -> analysisMapper.selectHouseOrCarExpenseStatements(projectId);
            case "TAX_EXPENSE" -> analysisMapper.selectTaxExpenseStatements(projectId);
            case "SINGLE_LARGE_INCOME" -> analysisMapper.selectSingleLargeIncomeStatements(
                projectId, toBigDecimal(config.getThresholdValue("SINGLE_TRANSACTION_AMOUNT"))
            );
            case "LARGE_CASH_DEPOSIT" -> analysisMapper.selectLargeCashDepositStatements(
                projectId, toBigDecimal(config.getThresholdValue("LARGE_CASH_DEPOSIT"))
            );
            case "LARGE_TRANSFER" -> analysisMapper.selectLargeTransferStatements(
                projectId, toBigDecimal(config.getThresholdValue("FREQUENT_TRANSFER"))
            );
            case "EXTERNAL_SINGLE_LARGE_AMOUNT" -> analysisMapper.selectExternalSingleLargeAmountStatements(
                projectId, toBigDecimal(config.getThresholdValue("FREQUENT_TRANSFER"))
            );
            case "ABNORMAL_CUSTOMER_TRANSACTION" -> analysisMapper.selectAbnormalCustomerTransactionStatements(projectId);
            case "EXTERNAL_NIGHT_TRANSACTION" -> analysisMapper.selectExternalNightTransactionStatements(projectId);
            case "GAMBLING_SENSITIVE_KEYWORD" -> analysisMapper.selectGamblingSensitiveKeywordStatements(projectId);
            case "EXTERNAL_GAMBLING_MEMO" -> analysisMapper.selectExternalGamblingMemoStatements(projectId);
            case "SPECIAL_AMOUNT_TRANSACTION" -> analysisMapper.selectSpecialAmountTransactionStatements(projectId);
            case "EXTERNAL_TO_STAFF_FAMILY_TRANSACTION" -> analysisMapper.selectExternalToStaffOrFamilyTransactionStatements(projectId);
            case "SUSPICIOUS_INCOME_KEYWORD" -> analysisMapper.selectSuspiciousIncomeKeywordStatements(projectId);
            case "HOUSE_REGISTRATION_MISMATCH" -> analysisMapper.selectHouseRegistrationMismatchStatements(projectId);
            case "PROPERTY_FEE_REGISTRATION_MISMATCH" -> analysisMapper.selectPropertyFeeRegistrationMismatchStatements(projectId);
            case "TAX_ASSET_REGISTRATION_MISMATCH" -> analysisMapper.selectTaxAssetRegistrationMismatchStatements(projectId);
            case "INCOME_ASSET_MISMATCH" -> analysisMapper.selectIncomeAssetMismatchStatements(projectId);
            case "FOREX_BUY_AMT" -> analysisMapper.selectForexBuyAmtStatements(
                projectId, toBigDecimal(config.getThresholdValue("SINGLE_PURCHASE_AMOUNT"))
            );
            case "FOREX_SELL_AMT" -> analysisMapper.selectForexSellAmtStatements(
                projectId, toBigDecimal(config.getThresholdValue("SINGLE_SETTLEMENT_AMOUNT"))
            );
            case "CROSS_BORDER_AMT" -> analysisMapper.selectCrossBorderAmtStatements(projectId);
            case "LARGE_PURCHASE_TRANSACTION" -> analysisMapper.selectLargePurchaseTransactionStatements(projectId);
            case "STOCK_TFR_LARGE" -> analysisMapper.selectStockTfrLargeStatements(
                projectId, toBigDecimal(config.getThresholdValue("STOCK_TFR_LARGE"))
            );
            case "LARGE_STOCK_TRADING" -> analysisMapper.selectLargeStockTradingStatements(
                projectId, toBigDecimal(config.getThresholdValue("STOCK_TFR_LARGE"))
            );
            default -> List.of();
        };
    }

    private List<BankTagObjectHitVO> executeObjectRule(Long projectId,
                                                       CcdiBankTagRule rule,
                                                       BankTagRuleExecutionConfig config) {
        return switch (rule.getRuleCode()) {
            case "CUMULATIVE_INCOME" -> analysisMapper.selectCumulativeIncomeObjects(
                projectId, toBigDecimal(config.getThresholdValue("CUMULATIVE_TRANSACTION_AMOUNT"))
            );
            case "EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT" -> analysisMapper.selectExternalCumulativeTransactionAmountObjects(
                projectId, toBigDecimal(config.getThresholdValue("CUMULATIVE_TRANSACTION_AMOUNT"))
            );
            case "ANNUAL_TURNOVER" -> analysisMapper.selectAnnualTurnoverObjects(
                projectId, toBigDecimal(config.getThresholdValue("ANNUAL_TURNOVER"))
            );
            case "EXTERNAL_ANNUAL_TURNOVER" -> analysisMapper.selectExternalAnnualTurnoverObjects(
                projectId, toBigDecimal(config.getThresholdValue("ANNUAL_TURNOVER"))
            );
            case "FREQUENT_CASH_DEPOSIT" -> analysisMapper.selectFrequentCashDepositObjects(
                projectId,
                toBigDecimal(config.getThresholdValue("LARGE_CASH_DEPOSIT")),
                toInteger(config.getThresholdValue("FREQUENT_CASH_DEPOSIT"))
            );
            case "LOW_INCOME_RELATIVE_LARGE_TRANSACTION" -> analysisMapper.selectLowIncomeRelativeLargeTransactionObjects(projectId);
            case "MULTI_PARTY_GAMBLING_TRANSFER" -> analysisMapper.selectMultiPartyGamblingTransferObjects(
                projectId,
                toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MIN")),
                toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MAX"))
            );
            case "EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER" -> analysisMapper.selectExternalMultiPartyGamblingTransferObjects(
                projectId,
                toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MIN")),
                toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MAX"))
            );
            case "MONTHLY_FIXED_INCOME" -> analysisMapper.selectMonthlyFixedIncomeObjects(
                projectId, toBigDecimal(config.getThresholdValue("MONTHLY_FIXED_INCOME"))
            );
            case "FIXED_COUNTERPARTY_TRANSFER" -> analysisMapper.selectFixedCounterpartyTransferObjects(
                projectId,
                toBigDecimal(config.getThresholdValue("FIXED_COUNTERPARTY_TRANSFER_MIN")),
                toBigDecimal(config.getThresholdValue("FIXED_COUNTERPARTY_TRANSFER_MAX"))
            );
            case "INTEREST_PAYMENT_BY_OTHERS" -> analysisMapper.selectInterestPaymentByOthersObjects(projectId);
            case "SUPPLIER_CONCENTRATION" -> analysisMapper.selectSupplierConcentrationObjects(projectId);
            case "WITHDRAW_CNT" -> analysisMapper.selectWithdrawCntObjects(
                projectId, toInteger(config.getThresholdValue("WITHDRAW_CNT"))
            );
            case "WITHDRAW_AMT" -> analysisMapper.selectWithdrawAmtObjects(
                projectId, toBigDecimal(config.getThresholdValue("WITHDRAW_AMT"))
            );
            case "SALARY_QUICK_TRANSFER" -> analysisMapper.selectSalaryQuickTransferObjects(projectId);
            case "SALARY_UNUSED" -> analysisMapper.selectSalaryUnusedObjects(projectId);
            case "SUDDEN_ACCOUNT_CLOSURE" -> analysisMapper.selectSuddenAccountClosureObjects(projectId);
            case "DORMANT_ACCOUNT_LARGE_ACTIVATION" -> analysisMapper.selectDormantAccountLargeActivationObjects(projectId);
            case "PROXY_ACCOUNT_OPERATION" -> analysisMapper.selectProxyAccountOperationObjects(projectId);
            default -> List.of();
        };
    }

    private List<CcdiBankTagResult> buildStatementResults(Long projectId,
                                                          CcdiBankTagRule rule,
                                                          List<BankTagStatementHitVO> hits,
                                                          String operator) {
        List<CcdiBankTagResult> results = new ArrayList<>();
        Date now = new Date();
        for (BankTagStatementHitVO hit : hits) {
            CcdiBankTagResult result = buildBaseResult(projectId, rule, operator, now);
            result.setBankStatementId(hit.getBankStatementId());
            result.setGroupId(hit.getGroupId());
            result.setLogId(hit.getLogId());
            result.setReasonDetail(hit.getReasonDetail());
            results.add(result);
        }
        return results;
    }

    private List<CcdiBankTagResult> buildObjectResults(Long projectId,
                                                       CcdiBankTagRule rule,
                                                       List<BankTagObjectHitVO> hits,
                                                       String operator) {
        List<CcdiBankTagResult> results = new ArrayList<>();
        Date now = new Date();
        for (BankTagObjectHitVO hit : hits) {
            CcdiBankTagResult result = buildBaseResult(projectId, rule, operator, now);
            result.setObjectType(hit.getObjectType() != null ? hit.getObjectType() : OBJECT_TYPE_STAFF_ID_CARD);
            result.setObjectKey(hit.getObjectKey());
            result.setReasonDetail(hit.getReasonDetail());
            results.add(result);
        }
        return results;
    }

    private CcdiBankTagResult buildBaseResult(Long projectId, CcdiBankTagRule rule, String operator, Date now) {
        CcdiBankTagResult result = new CcdiBankTagResult();
        result.setProjectId(projectId);
        result.setModelCode(rule.getModelCode());
        result.setModelName(rule.getModelName());
        result.setRuleCode(rule.getRuleCode());
        result.setRuleName(rule.getRuleName());
        result.setIndicatorCode(rule.getIndicatorCode());
        result.setResultType(rule.getResultType());
        result.setRiskLevel(rule.getRiskLevel());
        result.setBusinessCaliberSnapshot(rule.getBusinessCaliber());
        result.setCreateBy(operator);
        result.setCreateTime(now);
        result.setUpdateBy(operator);
        result.setUpdateTime(now);
        return result;
    }

    private BigDecimal toBigDecimal(String value) {
        if (value == null || value.isBlank()) {
            return BigDecimal.ZERO;
        }
        return new BigDecimal(value);
    }

    private Integer toInteger(String value) {
        if (value == null || value.isBlank()) {
            return 0;
        }
        return Integer.parseInt(value);
    }

    private void updateFailedTaskSafely(CcdiBankTagTask task, Exception ex) {
        task.setErrorMessage(buildSafeTaskErrorMessage(ex));
        try {
            taskMapper.updateTask(task);
        } catch (Exception updateEx) {
            log.error("【流水标签】写入任务失败状态异常: taskId={}, error={}",
                task.getId(), updateEx.getMessage(), updateEx);
            task.setErrorMessage(TASK_ERROR_MESSAGE_FALLBACK);
            taskMapper.updateTask(task);
        }
    }

    private static String buildSafeTaskErrorMessage(Throwable throwable) {
        if (throwable == null) {
            return TASK_ERROR_MESSAGE_FALLBACK;
        }

        StringBuilder builder = new StringBuilder();
        if (throwable.getMessage() != null && !throwable.getMessage().isBlank()) {
            builder.append(throwable.getMessage().trim());
        }

        Throwable rootCause = throwable;
        while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
            rootCause = rootCause.getCause();
        }

        if (rootCause != throwable && rootCause.getMessage() != null && !rootCause.getMessage().isBlank()) {
            String rootMessage = rootCause.getMessage().trim();
            if (!builder.toString().contains(rootMessage)) {
                if (!builder.isEmpty()) {
                    builder.append(" | rootCause=");
                }
                builder.append(rootMessage);
            }
        }

        return builder.isEmpty() ? TASK_ERROR_MESSAGE_FALLBACK : builder.toString();
    }
}
