package com.sundot.airport.ledger.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sundot.airport.common.core.domain.entity.SysDept;
import com.sundot.airport.common.core.domain.entity.SysUser;
import com.sundot.airport.common.enums.LedgerSyncFlagEnum;
import com.sundot.airport.common.utils.DateUtils;
import com.sundot.airport.ledger.domain.*;
import com.sundot.airport.ledger.mapper.*;
import com.sundot.airport.ledger.service.ILedgerSyncService;
import com.sundot.airport.system.mapper.SysDeptMapper;
import com.sundot.airport.system.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;

/**
 * 台账 → score_event 同步引擎
 *
 * 映射规则（indicator ID 与 score.sql 保持一致）：
 * ─────────────────────────────────────────────────────────
 * supervision_problem   → dim1 / 11(L2) / 111~113(L3, 按层级区分)
 * realtime_interception → dim1 / 12(L2) / 121~122(L3, 按层级区分)
 * security_test(未通过) → dim1 / 14(L2) / 141~143(L3, 按层级区分, 均-0.5)
 * unsafe_event          → dim1 / 13(L2) / 131~135(L3, 按类别区分)
 * seizure_stats         → dim1 / 15(L2) / 151~153(L3, 按类别区分)
 * reward_approval       → dim1 / 15(L2) / 151~153(L3, 按类别区分)
 * service_patrol        → dim2 / 22(L2) / 221~223(L3, 按层级区分)
 * complaint             → dim2 / 21(L2) / 无L3（叶子节点）
 * terminal_bonus        → dim2 / 23(L2) / 231(L3, 使用台账add_score)
 * exam_score(≥98)       → dim3 / 35(L2) / 351理论 | 352开机员(L3)
 *
 * 以下类型暂无台账，不同步（配分表分析.csv 标注"暂无台账，不考虑导入规则"）：
 *   banner_letter  → 锦旗及感谢信（服务响应/典型服务案例）
 *   reward_penalty → 部门奖惩记录（作风践行能力全部暂无台账）
 *   leave_special  → 请休假记录（身心调节能力全部暂无台账）
 */
@Service
public class LedgerSyncServiceImpl implements ILedgerSyncService {

    // ── Ledger Mappers ────────────────────────────────────────
    @Autowired private LedgerSupervisionProblemMapper supervisionMapper;
    @Autowired private LedgerRealtimeInterceptionMapper interceptionMapper;
    @Autowired private LedgerSecurityTestMapper securityTestMapper;
    @Autowired private LedgerUnsafeEventMapper unsafeEventMapper;
    @Autowired private LedgerSeizureStatsMapper seizureMapper;
    @Autowired private LedgerRewardApprovalMapper rewardApprovalMapper;
    @Autowired private LedgerServicePatrolMapper servicePatrolMapper;
    @Autowired private LedgerComplaintMapper complaintMapper;
    @Autowired private LedgerTerminalBonusMapper terminalBonusMapper;
    @Autowired private LedgerExamScoreMapper examScoreMapper;

    // ── Score Mappers ─────────────────────────────────────────
    @Autowired private ScoreEventMapper scoreEventMapper;
    @Autowired private ScoreDimensionMapper dimensionMapper;
    @Autowired private ScoreIndicatorMapper indicatorMapper;
    @Autowired private SysDeptMapper sysDeptMapper;
    @Autowired private SysUserMapper sysUserMapper;

    // ── Dimension ID cache (loaded once) ─────────────────────
    private final Map<String, Long> dimCache = new HashMap<>();
    private final Map<String, Long> deptCache = new HashMap<>();
    private final Map<String, Long> userCache = new HashMap<>();

    // ── Constants: Indicator IDs（与 score.sql 中的显式 ID 对应）─
    // dim1 安全防控能力
    private static final long IND_SUPERVISION_L2   = 11L;  // 员工规范化操作
    private static final long IND_SUPERVISION_L3_STATION  = 111L; // 站层级质控 -1.0
    private static final long IND_SUPERVISION_L3_DEPT     = 112L; // 部门层级质控 -0.8
    private static final long IND_SUPERVISION_L3_LEADER   = 113L; // 上级领导巡查 -1.2
    private static final long IND_INTERCEPT_L2     = 12L;  // 后台实时质控拦截
    private static final long IND_INTERCEPT_L3_STATION    = 121L; // 站层级 -1.0
    private static final long IND_INTERCEPT_L3_DEPT       = 122L; // 部门层级 -0.8
    private static final long IND_UNSAFE_L2        = 13L;  // 不安全事件
    private static final long IND_UNSAFE_L3_CAT1   = 131L; // 一类 -10
    private static final long IND_UNSAFE_L3_CAT2   = 132L; // 二类 -8
    private static final long IND_UNSAFE_L3_CAT3   = 133L; // 三类 -5
    private static final long IND_UNSAFE_L3_CAT4   = 134L; // 四类 -3
    private static final long IND_UNSAFE_L3_CAT5   = 135L; // 五类 -2 (默认)
    private static final long IND_SECTEST_L2       = 14L;  // 安保测试未通过
    private static final long IND_SECTEST_L3_GOV   = 141L; // 局方层级 -0.5
    private static final long IND_SECTEST_L3_CORP  = 142L; // 公司层级 -0.5
    private static final long IND_SECTEST_L3_STA   = 143L; // 站层级 -0.5
    private static final long IND_SEIZURE_L2       = 15L;  // 典型案例查获
    private static final long IND_SEIZURE_L3_A     = 151L; // 一类查获 +1.0
    private static final long IND_SEIZURE_L3_B     = 152L; // 二类查获 +0.5
    private static final long IND_SEIZURE_L3_C     = 153L; // 特殊查获 +0.15
    // dim2 服务响应能力
    private static final long IND_COMPLAINT_L2     = 21L;  // 旅客服务投诉（叶子节点，无L3）
    private static final long IND_SVCPATROL_L2     = 22L;  // 服务监察
    private static final long IND_SVCPATROL_L3_TERMINAL = 221L; // 航站楼及以上 -1.2
    private static final long IND_SVCPATROL_L3_STATION  = 222L; // 站层级 -1.0
    private static final long IND_SVCPATROL_L3_DEPT     = 223L; // 部门层级 -0.8
    private static final long IND_TYPCASE_L2       = 23L;  // 典型服务案例
    private static final long IND_TYPCASE_L3       = 231L; // 航站楼加分（分值取台账add_score）
    // dim3 业务实操能力
    private static final long IND_EXAM_L2          = 35L;  // 考试成绩
    private static final long IND_EXAM_L3_THEORY   = 351L; // 季度理论≥98 +5
    private static final long IND_EXAM_L3_MACHINE  = 352L; // 季度开机员≥98 +5

    // ── Score defaults ────────────────────────────────────────
    private static final BigDecimal NEG_ONE   = BigDecimal.valueOf(-1.00);
    private static final BigDecimal NEG_HALF  = BigDecimal.valueOf(-0.50);
    private static final BigDecimal NEG_08    = BigDecimal.valueOf(-0.80);
    private static final BigDecimal NEG_TWO   = BigDecimal.valueOf(-2.00);
    private static final BigDecimal POS_ONE   = BigDecimal.valueOf(1.00);
    private static final BigDecimal POS_HALF  = BigDecimal.valueOf(0.50);
    private static final BigDecimal POS_015   = BigDecimal.valueOf(0.15);
    private static final BigDecimal POS_07    = BigDecimal.valueOf(0.70);
    private static final BigDecimal POS_FIVE  = BigDecimal.valueOf(5.00);
    private static final BigDecimal ZERO      = BigDecimal.ZERO;

    // ═════════════════════════════════════════════════════════
    //  Public API
    // ═════════════════════════════════════════════════════════

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SyncResult syncAll() {
        try {
            loadDimCache();
            loadDeptCache();
            loadUserCache();
            StringBuilder sb = new StringBuilder();
            int totalIns = 0, totalSkip = 0;
            for (String type : Arrays.asList(
                    "supervision_problem", "realtime_interception", "security_test",
                    "unsafe_event", "seizure_stats", "reward_approval",
                    "service_patrol", "complaint", "terminal_bonus", "exam_score")) {
                SyncResult r = doSync(type);
                sb.append(type).append(":+").append(r.getTotalInserted())
                  .append("/skip").append(r.getTotalSkipped()).append("  ");
                totalIns += r.getTotalInserted();
                totalSkip += r.getTotalSkipped();
            }
            return new SyncResult(totalIns, totalSkip, sb.toString());
        } finally {
            // 同步完成后清除缓存
            clearCaches();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SyncResult syncByType(String ledgerType) {
        try {
            loadDimCache();
            loadDeptCache();
            loadUserCache();
            return doSync(ledgerType);
        } finally {
            // 同步完成后清除缓存
            clearCaches();
        }
    }

    // ═════════════════════════════════════════════════════════
    //  Router
    // ═════════════════════════════════════════════════════════

    private SyncResult doSync(String type) {
        switch (type) {
            case "supervision_problem":   return syncSupervisionProblem();
            case "realtime_interception": return syncRealtimeInterception();
            case "security_test":         return syncSecurityTest();
            case "unsafe_event":          return syncUnsafeEvent();
//            case "seizure_stats":         return syncSeizureStats();
            case "reward_approval":       return syncRewardApproval();
            case "service_patrol":        return syncServicePatrol();
            case "complaint":             return syncComplaint();
            case "terminal_bonus":        return syncTerminalBonus();
            case "exam_score":            return syncExamScore();
            default: return new SyncResult(0, 0, "unknown type: " + type);
        }
    }

    // ═════════════════════════════════════════════════════════
    //  Per-table sync methods
    // ═════════════════════════════════════════════════════════

    /** 1. 部门监察问题记录 → 安全防控能力/员工规范化操作（按问题层级区分L3） */
    private SyncResult syncSupervisionProblem() {
        LedgerSupervisionProblem query = new LedgerSupervisionProblem();
        query.setSyncFlag(LedgerSyncFlagEnum.ZERO.getCode());
        List<LedgerSupervisionProblem> list = supervisionMapper.selectList(query);
        Long dimId = dimCache.get("安全防控能力");
        ScoreIndicator lv2 = getIndicator(IND_SUPERVISION_L2);
        int ins = 0, skip = 0;
        for (LedgerSupervisionProblem row : list) {
            String src = "supervision_problem:" + row.getId();
            if (existsBySrc(src)) { skip++; continue; }
            if (!hasName(row.getInspectedName())) { skip++; continue; }
            // 按问题层级选取L3指标和默认分值
            String level = row.getProblemLevel() != null ? row.getProblemLevel() : "";
            long l3Id; BigDecimal defaultSv;
            if (level.contains("上级")) { l3Id = IND_SUPERVISION_L3_LEADER; defaultSv = BigDecimal.valueOf(-1.20); }
            else if (level.contains("部门")) { l3Id = IND_SUPERVISION_L3_DEPT; defaultSv = BigDecimal.valueOf(-0.80); }
            else if (level.contains("站")) { l3Id = IND_SUPERVISION_L3_STATION; defaultSv = NEG_ONE; }
            else { skip++; continue; }
            BigDecimal sv = row.getDeductScore() != null ? row.getDeductScore().negate() : defaultSv;
            ScoreIndicator lv3 = getIndicator(l3Id);
            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getInspectedName(),
                    row.getTeamName(), row.getRecordDate(),
                    row.getLocation(), sv, ZERO,
                    row.getProblemDesc(), src, row.getEvidenceFile());
            scoreEventMapper.insert(e);
            ins++;
        }
        supervisionMapper.updateSyncFlag();
        return new SyncResult(ins, skip, "");
    }

    /** 2. 实时质控拦截 → 安全防控能力/后台实时质控拦截（按问题层级区分L3） */
    private SyncResult syncRealtimeInterception() {
        LedgerRealtimeInterception query = new LedgerRealtimeInterception();
        query.setSyncFlag(LedgerSyncFlagEnum.ZERO.getCode());
        List<LedgerRealtimeInterception> list = interceptionMapper.selectList(new LedgerRealtimeInterception());
        Long dimId = dimCache.get("安全防控能力");
        ScoreIndicator lv2 = getIndicator(IND_INTERCEPT_L2);
        int ins = 0, skip = 0;
        for (LedgerRealtimeInterception row : list) {
            String src = "realtime_interception:" + row.getId();
            if (existsBySrc(src)) { skip++; continue; }
            if (!hasName(row.getInspectorName())) { skip++; continue; }
            // 取【问题层级】字段，站层级扣1分，部门层级扣0.8分，其它层级跳过
            String level = row.getProblemLevel() != null ? row.getProblemLevel() : "";
            long l3Id; BigDecimal sv;
            if (level.contains("站")) {
                l3Id = IND_INTERCEPT_L3_STATION;
                sv = row.getDeductScore() != null ? row.getDeductScore().negate() : NEG_ONE;
            } else if (level.contains("部门")) {
                l3Id = IND_INTERCEPT_L3_DEPT;
                sv = row.getDeductScore() != null ? row.getDeductScore().negate() : NEG_08;
            } else {
                // todo其它层级暂时跳过
                skip++;
                continue;
            }
            ScoreIndicator lv3 = getIndicator(l3Id);
            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getInspectorName(),
                    row.getTeamName(), row.getRecordDate(),
                    row.getChannelNo(), sv, ZERO,
                    row.getItemName(), src, row.getEvidenceFile());
            scoreEventMapper.insert(e);
            ins++;
        }
        interceptionMapper.updateSyncFlag();
        return new SyncResult(ins, skip, "");
    }

    /** 3. 安保测试（仅"未通过"才计分，按层级选L3，分值均为-0.5） */
    private SyncResult syncSecurityTest() {
        LedgerSecurityTest query = new LedgerSecurityTest();
        query.setSyncFlag(LedgerSyncFlagEnum.ZERO.getCode());
        List<LedgerSecurityTest> list = securityTestMapper.selectList(query);
        Long dimId = dimCache.get("安全防控能力");
        ScoreIndicator lv2 = getIndicator(IND_SECTEST_L2);
        int ins = 0, skip = 0;
        for (LedgerSecurityTest row : list) {
            String src = "security_test:" + row.getId();
            if (existsBySrc(src)) { skip++; continue; }
            if (!"未通过".equals(row.getTestResult())) { skip++; continue; }
            if (!hasName(row.getTestedName())) { skip++; continue; }
            // 取层级字段（scoreIndicator），而不是testType
            String level = row.getScoreIndicator() != null ? row.getScoreIndicator() : "";
            long l3Id;
            if (level.contains("局方")) { l3Id = IND_SECTEST_L3_GOV; }
            else if (level.contains("公司")) { l3Id = IND_SECTEST_L3_CORP; }
            else if (level.contains("站")) { l3Id = IND_SECTEST_L3_STA; }
            else { skip++; continue; }
            ScoreIndicator lv3 = getIndicator(l3Id);
            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getTestedName(),
                    row.getTeamName(), row.getRecordDate(),
                    null, NEG_HALF, ZERO,
                    row.getTestItem(), src, row.getEvidenceFile());
            scoreEventMapper.insert(e);
            ins++;
        }
        securityTestMapper.updateSyncFlag();
        return new SyncResult(ins, skip, "");
    }

    /** 4. 不安全事件（按类别选L3和默认分值） */
    private SyncResult syncUnsafeEvent() {
        LedgerUnsafeEvent query = new LedgerUnsafeEvent();
        query.setSyncFlag(LedgerSyncFlagEnum.ZERO.getCode());
        List<LedgerUnsafeEvent> list = unsafeEventMapper.selectList(query);
        Long dimId = dimCache.get("安全防控能力");
        ScoreIndicator lv2 = getIndicator(IND_UNSAFE_L2);
        int ins = 0, skip = 0;
        for (LedgerUnsafeEvent row : list) {
            String src = "unsafe_event:" + row.getId();
            if (existsBySrc(src)) { skip++; continue; }
            if (!hasName(row.getResponsibleName())) { skip++; continue; }
            String cat = row.getEventType() != null ? row.getEventType() : "";
            long l3Id; BigDecimal defaultSv;
            if (cat.contains("一类"))      { l3Id = IND_UNSAFE_L3_CAT1; defaultSv = BigDecimal.valueOf(-10.00); }
            else if (cat.contains("二类")) { l3Id = IND_UNSAFE_L3_CAT2; defaultSv = BigDecimal.valueOf(-8.00); }
            else if (cat.contains("三类")) { l3Id = IND_UNSAFE_L3_CAT3; defaultSv = BigDecimal.valueOf(-5.00); }
            else if (cat.contains("四类")) { l3Id = IND_UNSAFE_L3_CAT4; defaultSv = BigDecimal.valueOf(-3.00); }
            else                           { l3Id = IND_UNSAFE_L3_CAT5; defaultSv = NEG_TWO; }
            BigDecimal sv = row.getDeductScore() != null ? row.getDeductScore().negate() : defaultSv;
            ScoreIndicator lv3 = getIndicator(l3Id);
            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getResponsibleName(),
                    row.getTeamName(), row.getRecordDate(),
                    null, sv, ZERO,
                    row.getEventDesc(), src, row.getEvidenceFile());
            scoreEventMapper.insert(e);
            ins++;
        }
        unsafeEventMapper.updateSyncFlag();
        return new SyncResult(ins, skip, "");
    }

    /** 5. 查获违规品统计（加分，按类别选L3） */
    private SyncResult syncSeizureStats() {
        LedgerSeizureStats query = new LedgerSeizureStats();
        query.setSyncFlag(LedgerSyncFlagEnum.ZERO.getCode());
        List<LedgerSeizureStats> list = seizureMapper.selectList(query);
        Long dimId = dimCache.get("安全防控能力");
        ScoreIndicator lv2 = getIndicator(IND_SEIZURE_L2);
        int ins = 0, skip = 0;
        for (LedgerSeizureStats row : list) {
            String src = "seizure_stats:" + row.getId();
            if (existsBySrc(src)) { skip++; continue; }
            if (!hasName(row.getInspectorName())) { skip++; continue; }
            String cat = row.getItemCategory() != null ? row.getItemCategory() : "";
            long l3Id; BigDecimal sv;
            if (cat.contains("一类"))      { l3Id = IND_SEIZURE_L3_A; sv = POS_ONE; }
            else if (cat.contains("二类")) { l3Id = IND_SEIZURE_L3_B; sv = POS_HALF; }
            else                           { l3Id = IND_SEIZURE_L3_C; sv = POS_015; }
            BigDecimal cascade = row.getStackedScore() != null ? row.getStackedScore() : ZERO;
            ScoreIndicator lv3 = getIndicator(l3Id);
            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getInspectorName(),
                    row.getTeamName(), row.getRecordDate(),
                    null, sv, cascade,
                    row.getItemName(), src, row.getEvidenceFile());
            scoreEventMapper.insert(e);
            ins++;
        }
        seizureMapper.updateSyncFlag();
        return new SyncResult(ins, skip, "");
    }

    /** 6. 小额奖励审批单（安全类查获加分，按类别选L3） */
    private SyncResult syncRewardApproval() {
        LedgerRewardApproval query = new LedgerRewardApproval();
        query.setSyncFlag(LedgerSyncFlagEnum.ZERO.getCode());
        List<LedgerRewardApproval> list = rewardApprovalMapper.selectList(query);
        Long dimId = dimCache.get("安全防控能力");
        ScoreIndicator lv2 = getIndicator(IND_SEIZURE_L2);
        int ins = 0, skip = 0;
        for (LedgerRewardApproval row : list) {
            String src = "reward_approval:" + row.getId();
            if (existsBySrc(src)) { skip++; continue; }
            if (!hasName(row.getPersonName())) { skip++; continue; }
            String cat = row.getRewardType() != null ? row.getRewardType() : "";
            long l3Id; BigDecimal sv;
            if (cat.contains("一类"))      { l3Id = IND_SEIZURE_L3_A; sv = POS_ONE; }
            else if (cat.contains("二类")) { l3Id = IND_SEIZURE_L3_B; sv = POS_HALF; }
            else                           { l3Id = IND_SEIZURE_L3_C; sv = POS_015; }
            ScoreIndicator lv3 = getIndicator(l3Id);
            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getPersonName(),
                    row.getTeamName(), row.getApproveDate() != null ? new java.util.Date(row.getApproveDate().getTime()) : null,
                    null, sv, ZERO,
                    row.getRemark(), src, null);
            scoreEventMapper.insert(e);
            ins++;
        }
        rewardApprovalMapper.updateSyncFlag();
        return new SyncResult(ins, skip, "");
    }

    /** 7. 服务巡查 → 服务响应能力/服务监察（按问题层级区分L3） */
    private SyncResult syncServicePatrol() {
        LedgerServicePatrol query = new LedgerServicePatrol();
        query.setSyncFlag(LedgerSyncFlagEnum.ZERO.getCode());
        List<LedgerServicePatrol> list = servicePatrolMapper.selectList(query);
        Long dimId = dimCache.get("服务响应能力");
        ScoreIndicator lv2 = getIndicator(IND_SVCPATROL_L2);
        int ins = 0, skip = 0;
        for (LedgerServicePatrol row : list) {
            String src = "service_patrol:" + row.getId();
            if (existsBySrc(src)) { skip++; continue; }
            if (!hasName(row.getInspectedName())) { skip++; continue; }
            String level = row.getProblemLevel() != null ? row.getProblemLevel() : "";
            long l3Id; BigDecimal defaultSv;
            if (level.contains("航站楼")) { l3Id = IND_SVCPATROL_L3_TERMINAL; defaultSv = BigDecimal.valueOf(-1.20); }
            else if (level.contains("站层级") || level.contains("站级")) { l3Id = IND_SVCPATROL_L3_STATION; defaultSv = NEG_ONE; }
            else if (level.contains("部门")) { l3Id = IND_SVCPATROL_L3_DEPT; defaultSv = NEG_08; }
            else { skip++; continue; }
            BigDecimal sv = row.getDeductScore() != null ? row.getDeductScore().negate() : defaultSv;
            ScoreIndicator lv3 = getIndicator(l3Id);
            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getInspectedName(),
                    row.getTeamName(), row.getRecordDate(),
                    row.getLocation(), sv, ZERO,
                    row.getProblemDesc(), src, row.getEvidenceFile());
            scoreEventMapper.insert(e);
            ins++;
        }
        servicePatrolMapper.updateSyncFlag();
        return new SyncResult(ins, skip, "");
    }

    /** 8. 投诉情况（有责任人才计分；旅客服务投诉为L2叶子节点，无L3） */
    private SyncResult syncComplaint() {
        LedgerComplaint quey = new LedgerComplaint();
        quey.setSyncFlag(LedgerSyncFlagEnum.ZERO.getCode());
        List<LedgerComplaint> list = complaintMapper.selectList(quey);
        Long dimId = dimCache.get("服务响应能力");
        ScoreIndicator lv2 = getIndicator(IND_COMPLAINT_L2);
        int ins = 0, skip = 0;
        for (LedgerComplaint row : list) {
            String src = "complaint:" + row.getId();
            if (existsBySrc(src)) { skip++; continue; }
            if (!hasName(row.getResponsibleName())) { skip++; continue; }
            BigDecimal sv = row.getDeductScore() != null ? row.getDeductScore().negate() : NEG_TWO;
            ScoreEvent e = buildEvent(dimId, lv2, null, row.getResponsibleName(),
                    row.getTeamName(), row.getRecordDate(),
                    null, sv, ZERO,
                    row.getComplaintDesc(), src, row.getEvidenceFile());
            scoreEventMapper.insert(e);
            ins++;
        }
        complaintMapper.updateSyncFlag();
        return new SyncResult(ins, skip, "");
    }

    /** 9. 航站楼加分 → 服务响应能力/典型服务案例/航站楼加分（分值取台账add_score） */
    private SyncResult syncTerminalBonus() {
        LedgerTerminalBonus query = new LedgerTerminalBonus();
        query.setSyncFlag(LedgerSyncFlagEnum.ZERO.getCode());
        List<LedgerTerminalBonus> list = terminalBonusMapper.selectList(query);
        Long dimId = dimCache.get("服务响应能力");
        ScoreIndicator lv2 = getIndicator(IND_TYPCASE_L2);
        ScoreIndicator lv3 = getIndicator(IND_TYPCASE_L3);
        int ins = 0, skip = 0;
        for (LedgerTerminalBonus row : list) {
            String src = "terminal_bonus:" + row.getId();
            if (existsBySrc(src)) { skip++; continue; }
            if (!hasName(row.getPersonName())) { skip++; continue; }
            BigDecimal sv = row.getAddScore() != null ? row.getAddScore() : POS_07;
            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getPersonName(),
                    row.getTeamName(), row.getApproveDate() != null ? new java.util.Date(row.getApproveDate().getTime()) : null,
                    null, sv, ZERO,
                    "航站楼加分", src, null);
            scoreEventMapper.insert(e);
            ins++;
        }
        terminalBonusMapper.updateSyncFlag();
        return new SyncResult(ins, skip, "");
    }

    /** 10. 成绩收集（≥98加5分；其余不计分，暂无台账规则） */
    private SyncResult syncExamScore() {
        LedgerExamScore query = new LedgerExamScore();
        query.setSyncFlag(LedgerSyncFlagEnum.ZERO.getCode());
        List<LedgerExamScore> list = examScoreMapper.selectList(query);
        Long dimId = dimCache.get("业务实操能力");
        ScoreIndicator lv2 = getIndicator(IND_EXAM_L2);
        int ins = 0, skip = 0;
        for (LedgerExamScore row : list) {
            String src = "exam_score:" + row.getId();
            if (existsBySrc(src)) { skip++; continue; }
            if (!hasName(row.getPersonName())) { skip++; continue; }
            if (row.getTheoryScore() == null) { skip++; continue; }
            double score = row.getTheoryScore().doubleValue();
            // 仅≥98分加分；补考扣分规则暂无台账，不处理
            if (score < 98) { skip++; continue; }
            // 按科目区分L3（图像成绩→开机员能力，其余→理论）
            String category = row.getExamCategory() != null ? row.getExamCategory() : "";
            long l3Id = category.contains("图像") || category.contains("开机")
                    ? IND_EXAM_L3_MACHINE : IND_EXAM_L3_THEORY;
            ScoreIndicator lv3 = getIndicator(l3Id);
            ScoreEvent e = buildEvent(dimId, lv2, lv3, row.getPersonName(),
                    row.getTeamName(), null,
                    null, POS_FIVE, ZERO,
                    row.getExamCategory() + " " + row.getExamPeriod(), src, null);
            scoreEventMapper.insert(e);
            ins++;
        }
        examScoreMapper.updateSyncFlag();
        return new SyncResult(ins, skip, "");
    }

    // ═════════════════════════════════════════════════════════
    //  Helpers
    // ═════════════════════════════════════════════════════════

    private void loadDimCache() {
        if (!dimCache.isEmpty()) return;
        dimensionMapper.selectList(new ScoreDimension()).forEach(d -> dimCache.put(d.getName(), d.getId()));
    }

    private void loadDeptCache() {
        if (!deptCache.isEmpty()) return;
        sysDeptMapper.selectDeptList(new SysDept()).forEach(d -> deptCache.put(d.getDeptName(), d.getDeptId()));
    }

    private void loadUserCache() {
        if (!userCache.isEmpty()) return;
        sysUserMapper.selectUserList(new SysUser()).forEach(d -> userCache.put(d.getNickName(), d.getUserId()));
    }

    /**
     * 清除所有缓存
     */
    private void clearCaches() {
        dimCache.clear();
        deptCache.clear();
        userCache.clear();
        indCache.clear();
        indicatorNameToIdCache.clear();
    }

    /**
     * 通过用户名称解析组织信息（ID和名称）
     * @param personName 人员名称
     * @return Map包含: userId, deptId, deptName, teamId, teamName, groupId, groupName
     */
    private Map<String, Object> resolveOrgInfoByName(String personName) {
        Map<String, Object> result = new HashMap<>();
        if (personName == null || personName.trim().isEmpty()) {
            return result;
        }

        String name = personName.trim();
        
        // 1. 从缓存中查找用户
        Long userId = userCache.get(name);
        if (userId == null) {
            return result;
        }
        result.put("userId", userId);

        // 2. 查询用户详情获取部门ID
        SysUser user = sysUserMapper.selectUserById(userId);
        if (user == null || user.getDeptId() == null) {
            return result;
        }

        // 3. 从缓存中获取用户部门信息
        SysDept userDept = sysDeptMapper.selectDeptById(user.getDeptId());
        if (userDept == null) {
            return result;
        }

        String deptType = userDept.getDeptType();

        // 4. 根据部门类型递归获取各级组织ID和名称
        if ("BRIGADE".equals(deptType)) {
            // 用户部门是大队
            result.put("deptId", user.getDeptId());
            result.put("deptName", userDept.getDeptName());
        } else if ("MANAGER".equals(deptType)) {
            // 用户部门是队室/班组，向上找大队
            result.put("teamId", user.getDeptId());
            result.put("teamName", userDept.getDeptName());
            if (userDept.getParentId() != null && userDept.getParentId() != 0) {
                result.put("deptId", userDept.getParentId());
                // 获取父级部门（大队）
                SysDept parentDept = sysDeptMapper.selectDeptById(userDept.getParentId());
                if (parentDept != null) {
                    result.put("deptName", parentDept.getDeptName());
                }
            }
        } else if ("TEAMS".equals(deptType)) {
            // 用户部门是小组，向上找队室和大队
            result.put("groupId", user.getDeptId());
            result.put("groupName", userDept.getDeptName());
            if (userDept.getParentId() != null && userDept.getParentId() != 0) {
                result.put("teamId", userDept.getParentId());
                // 获取父级部门（队室）
                SysDept parentDept = sysDeptMapper.selectDeptById(userDept.getParentId());
                if (parentDept != null) {
                    result.put("teamName", parentDept.getDeptName());
                    if (parentDept.getParentId() != null && parentDept.getParentId() != 0) {
                        result.put("deptId", parentDept.getParentId());
                        // 获取祖父级部门（大队）
                        SysDept grandParentDept = sysDeptMapper.selectDeptById(parentDept.getParentId());
                        if (grandParentDept != null) {
                            result.put("deptName", grandParentDept.getDeptName());
                        }
                    }
                }
            }
        }

        return result;
    }

    private final Map<Long, ScoreIndicator> indCache = new HashMap<>();
    private final Map<String, Long> indicatorNameToIdCache = new HashMap<>(); // 指标名称到ID的映射
    
    private ScoreIndicator getIndicator(long id) {
        return indCache.computeIfAbsent(id, indicatorMapper::selectById);
    }

    private boolean existsBySrc(String sourceLedger) {
        return scoreEventMapper.selectCount(
                new LambdaQueryWrapper<ScoreEvent>()
                        .eq(ScoreEvent::getSourceLedger, sourceLedger)) > 0;
    }

    private boolean hasName(String name) {
        return StringUtils.hasText(name);
    }

    private ScoreEvent buildEvent(Long dimId, ScoreIndicator lv2, ScoreIndicator lv3,
                                  String personName, String teamName, java.util.Date eventDate,
                                  String location, BigDecimal sv, BigDecimal cascade,
                                  String desc, String sourceLedger, String evidenceFile) {
        ScoreEvent e = new ScoreEvent();
        e.setDimensionId(dimId);
        e.setDimensionName(dimId != null ? dimCache.entrySet().stream()
                .filter(x -> x.getValue().equals(dimId)).map(Map.Entry::getKey).findFirst().orElse("") : "");
        if (lv2 != null) {
            e.setIndicatorId(lv2.getId());
            e.setLevel2Id(lv2.getId());
            e.setLevel2Name(lv2.getName());
        }
        if (lv3 != null) {
            e.setIndicatorId(lv3.getId());
            e.setLevel3Id(lv3.getId());
            e.setLevel3Name(lv3.getName());
            // 如果lv3有父级（即lv2），确保level2Id也被设置
            if (lv3.getParentId() != null && lv3.getParentId() != 0) {
                e.setLevel2Id(lv3.getParentId());
                // 通过ID获取lv2的名称（如果之前没设置）
                if (e.getLevel2Name() == null) {
                    ScoreIndicator parentLv2 = getIndicator(lv3.getParentId());
                    if (parentLv2 != null) {
                        e.setLevel2Name(parentLv2.getName());
                    }
                }
            }
        } else if (lv2 != null) {
            e.setIndicatorId(lv2.getId());
        }
        if (eventDate != null) e.setEventTime(eventDate);
        else e.setEventTime(DateUtils.getNowDate());
        e.setLocation(location);
        e.setPersonName(personName);
        e.setTeamName(teamName);
        e.setScoreValue(sv != null ? sv : ZERO);
        e.setCascadeScore(cascade != null ? cascade : ZERO);
        e.setTotalScore(e.getScoreValue().add(e.getCascadeScore()));
        e.setEventDesc(desc != null && desc.length() > 500 ? desc.substring(0, 500) : desc);
        e.setSourceType("2"); // 台账同步
        e.setSourceLedger(sourceLedger);
        e.setEvidenceFile(evidenceFile);
        e.setCreateBy("system");
        e.setCreateTime(DateUtils.getNowDate());
        
        // 通过人员名称解析组织信息（ID和名称）
        if (StrUtil.isNotEmpty(e.getPersonName())) {
            Map<String, Object> orgInfo = resolveOrgInfoByName(e.getPersonName());
            if (!orgInfo.isEmpty()) {
                e.setPersonId((Long) orgInfo.get("userId"));
                e.setDeptId((Long) orgInfo.get("deptId"));
                e.setDeptName((String) orgInfo.get("deptName"));
                e.setTeamId((Long) orgInfo.get("teamId"));
                e.setTeamName((String) orgInfo.get("teamName"));
                e.setGroupId((Long) orgInfo.get("groupId"));
                e.setGroupName((String) orgInfo.get("groupName"));
            }
        }
        
        return e;
    }
}
