package com.writeoff.module.expert.service;

import com.writeoff.common.api.PageResult;
import com.writeoff.common.exception.BusinessException;
import com.writeoff.common.model.ImportResult;
import com.writeoff.common.util.ImportValidationUtils;
import com.writeoff.module.expert.dto.AddBankCardRequest;
import com.writeoff.module.expert.dto.CreateExpertRequest;
import com.writeoff.module.expert.dto.MergeExpertRequest;
import com.writeoff.module.file.service.OssService;
import com.writeoff.module.expert.model.ExpertBankCardInfo;
import com.writeoff.module.expert.model.ExpertInfo;
import com.writeoff.module.system.service.DataPermissionService;
import com.writeoff.security.AuthContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class ExpertService {
    /**
     * 专家主数据改为平台级共享，统一落在 tenant_id=0。
     */
    private static final long PLATFORM_TENANT_ID = 0L;

    private static final String DICT_TYPE_EXPERT_TITLE = "EXPERT_TITLE";
    private static final String DICT_TYPE_EXPERT_HOSPITAL = "EXPERT_HOSPITAL";

    private final JdbcTemplate jdbcTemplate;
    private final OssService ossService;
    private final DataPermissionService dataPermissionService;

    private static final RowMapper<ExpertInfo> EXPERT_ROW_MAPPER = (rs, n) -> new ExpertInfo(
            rs.getLong("id"),
            rs.getString("expert_name"),
            rs.getString("gender"),
            rs.getString("birthday"),
            rs.getString("id_no"),
            rs.getString("id_card_valid_until"),
            rs.getString("id_card_front_oss_key"),
            rs.getString("id_card_back_oss_key"),
            rs.getString("phone"),
            rs.getString("title_code"),
            rs.getString("title_name"),
            rs.getString("hospital_code"),
            rs.getString("hospital_name"),
            rs.getString("status"),
            rs.getString("status_reason"),
            rs.getString("status_changed_at"),
            rs.getInt("export_restricted") == 1
    );

    private static final RowMapper<ExpertBankCardInfo> CARD_ROW_MAPPER = (rs, n) -> new ExpertBankCardInfo(
            rs.getLong("id"),
            rs.getLong("expert_id"),
            rs.getString("bank_name"),
            rs.getString("bank_province"),
            rs.getString("bank_city"),
            rs.getString("bank_branch_name"),
            rs.getString("bank_card_no"),
            rs.getString("bank_card_front_oss_key"),
            rs.getString("bank_card_back_oss_key"),
            rs.getString("account_name"),
            rs.getString("is_default"),
            rs.getString("card_status"),
            rs.getInt("inconsistent_name_approved") == 1,
            rs.getString("change_reason")
    );

    public ExpertService(JdbcTemplate jdbcTemplate, OssService ossService, DataPermissionService dataPermissionService) {
        this.jdbcTemplate = jdbcTemplate;
        this.ossService = ossService;
        this.dataPermissionService = dataPermissionService;
    }

    public Map<String, Object> presignAssetUpload(String fileName, String contentType) {
        String name = fileName == null ? "" : fileName.trim();
        if (name.isEmpty()) {
            throw new BusinessException(10001, "文件名不能为空");
        }
        String ext = "";
        int idx = name.lastIndexOf('.');
        if (idx > 0 && idx < name.length() - 1) {
            ext = "." + name.substring(idx + 1).toLowerCase();
        }
        String normalizedType = normalizeContentType(contentType);
        String objectKey = "expert/asset/" + PLATFORM_TENANT_ID + "/" + safeUserId() + "/" + UUID.randomUUID().toString().replace("-", "") + ext;
        String uploadUrl = ossService.generateUploadUrl(objectKey, normalizedType);
        Map<String, Object> data = new LinkedHashMap<String, Object>();
        data.put("objectKey", objectKey);
        data.put("uploadUrl", uploadUrl);
        data.put("contentType", normalizedType);
        data.put("method", "PUT");
        return data;
    }

    public PageResult<ExpertInfo> list(String keyword, int pageNo, int pageSize) {
        int safePage = Math.max(pageNo, 1);
        int safeSize = Math.min(Math.max(pageSize, 1), 100);
        int offset = (safePage - 1) * safeSize;

        StringBuilder whereClause = new StringBuilder(
                "WHERE e.tenant_id=" + PLATFORM_TENANT_ID + " AND e.is_deleted=0"
        );
        List<Object> countArgs = new ArrayList<>();
        if (keyword != null && !keyword.trim().isEmpty()) {
            String kw = keyword.trim().replace("'", "''");
            whereClause.append(" AND (e.expert_name LIKE '%").append(kw).append("%' OR e.id_no LIKE '%").append(kw).append("%' OR e.phone LIKE '%").append(kw).append("%')");
        }

        Integer total = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM expert e " + whereClause,
                Integer.class
        );
        long totalCount = total == null ? 0 : total;

        StringBuilder sql = new StringBuilder(
                "SELECT e.id, e.expert_name, e.gender, e.birthday, e.id_no, e.id_card_valid_until, e.id_card_front_oss_key, e.id_card_back_oss_key, e.phone, " +
                        "e.title_code, IFNULL(dt.dict_name, e.title) AS title_name, " +
                        "e.hospital_code, IFNULL(dh.dict_name, e.organization) AS hospital_name, " +
                        "e.status, e.status_reason, e.status_changed_at, e.export_restricted " +
                        "FROM expert e " +
                        "LEFT JOIN platform_dictionary_item dt ON dt.dict_type='EXPERT_TITLE' AND dt.dict_code=e.title_code AND dt.is_deleted=0 " +
                        "LEFT JOIN platform_dictionary_item dh ON dh.dict_type='EXPERT_HOSPITAL' AND dh.dict_code=e.hospital_code AND dh.is_deleted=0 "
        );
        sql.append(whereClause);
        sql.append(" ORDER BY e.id DESC LIMIT ").append(safeSize).append(" OFFSET ").append(offset);
        List<ExpertInfo> list = jdbcTemplate.query(sql.toString(), EXPERT_ROW_MAPPER);
        list = filterByExpertScope(list);
        List<ExpertInfo> maskedList = new java.util.ArrayList<ExpertInfo>(list.size());
        for (ExpertInfo item : list) {
            maskedList.add(maskSensitiveFields(item));
        }
        return new PageResult<ExpertInfo>(maskedList, totalCount, safePage, safeSize);
    }

    public ExpertInfo get(Long id) {
        ExpertInfo expert = findById(id);
        assertExpertAccessible(expert.getId());
        return expert;
    }

    /**
     * 按专家主键批量查询姓名与医院展示名（字典名优先，否则 organization），用于会议资料审核等只读展示。
     */
    public Map<Long, Map<String, Object>> mapExpertDisplayByIds(Collection<Long> expertIds) {
        if (expertIds == null || expertIds.isEmpty()) {
            return new LinkedHashMap<>();
        }
        List<Long> idList = expertIds.stream()
                .filter(Objects::nonNull)
                .mapToLong(Long::longValue)
                .filter(id -> id > 0)
                .distinct()
                .boxed()
                .collect(Collectors.toList());
        if (idList.isEmpty()) {
            return new LinkedHashMap<>();
        }
        String placeholders = idList.stream().map(id -> "?").collect(Collectors.joining(","));
        String sql = "SELECT e.id, e.expert_name, IFNULL(dh.dict_name, e.organization) AS hospital_name " +
                "FROM expert e " +
                "LEFT JOIN platform_dictionary_item dh ON dh.dict_type='" + DICT_TYPE_EXPERT_HOSPITAL + "' " +
                "AND dh.dict_code=e.hospital_code AND dh.is_deleted=0 " +
                "WHERE e.tenant_id=? AND e.id IN (" + placeholders + ") AND e.is_deleted=0";
        List<Object> args = new ArrayList<>();
        args.add(PLATFORM_TENANT_ID);
        args.addAll(idList);
        return jdbcTemplate.query(sql, rs -> {
            Map<Long, Map<String, Object>> m = new LinkedHashMap<>();
            while (rs.next()) {
                long id = rs.getLong("id");
                Map<String, Object> row = new LinkedHashMap<>();
                row.put("expertId", id);
                row.put("expertName", rs.getString("expert_name"));
                String hospital = rs.getString("hospital_name");
                row.put("hospital", hospital != null ? hospital : "");
                m.put(id, row);
            }
            return m;
        }, args.toArray());
    }

    @Transactional(rollbackFor = Exception.class)
    public ExpertInfo create(CreateExpertRequest request) {
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM expert WHERE tenant_id=? AND id_no=? AND is_deleted=0",
                Integer.class,
                PLATFORM_TENANT_ID,
                request.getIdNo()
        );
        if (count != null && count > 0) {
            throw new BusinessException(10001, "身份证号已存在");
        }
        DictionaryItem titleItem = resolveDictionaryItem(DICT_TYPE_EXPERT_TITLE, request.getTitleCode(), request.getTitle(), "职称");
        DictionaryItem hospitalItem = resolveDictionaryItem(DICT_TYPE_EXPERT_HOSPITAL, request.getHospitalCode(), request.getOrganization(), "医院");
        jdbcTemplate.update(
                "INSERT INTO expert (tenant_id, expert_name, gender, birthday, id_no, id_card_valid_until, id_card_front_oss_key, id_card_back_oss_key, phone, title_code, title, hospital_code, organization, status, status_reason, status_changed_by, status_changed_at, export_restricted, created_by, updated_by) " +
                        "VALUES (?, ?, ?, STR_TO_DATE(?, '%Y-%m-%d'), ?, STR_TO_DATE(?, '%Y-%m-%d'), ?, ?, ?, ?, ?, ?, ?, 'ENABLED', ?, ?, NOW(), ?, ?, ?)",
                PLATFORM_TENANT_ID,
                request.getExpertName(),
                request.getGender(),
                request.getBirthday(),
                request.getIdNo(),
                request.getIdCardValidUntil(),
                request.getIdCardFrontOssKey(),
                request.getIdCardBackOssKey(),
                request.getPhone(),
                titleItem.getDictCode(),
                titleItem.getDictName(),
                hospitalItem.getDictCode(),
                hospitalItem.getDictName(),
                request.getStatusReason(),
                safeUserId(),
                Boolean.TRUE.equals(request.getExportRestricted()) ? 1 : 0,
                safeUserId(),
                safeUserId()
        );
        Long id = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id),0) FROM expert WHERE tenant_id=?", Long.class, PLATFORM_TENANT_ID);
        return findById(id == null ? 0L : id);
    }

    public ImportResult importExperts(List<CreateExpertRequest> experts) {
        ImportResult result = new ImportResult();
        result.setTotal(experts == null ? 0 : experts.size());
        if (experts == null) {
            return result;
        }
        Set<String> batchIdNos = new HashSet<String>();
        Set<String> batchPhones = new HashSet<String>();
        for (int i = 0; i < experts.size(); i++) {
            CreateExpertRequest item = experts.get(i);
            int rowNo = i + 2;
            try {
                validateImportExpert(item, batchIdNos, batchPhones);
                create(item);
                result.markSuccess();
            } catch (Exception ex) {
                result.addError(rowNo, buildExpertIdentifier(item), resolveImportMessage(ex));
            }
        }
        return result;
    }

    @Transactional(rollbackFor = Exception.class)
    public ExpertInfo update(Long id, CreateExpertRequest request) {
        assertExpertExists(id);
        DictionaryItem titleItem = resolveDictionaryItem(DICT_TYPE_EXPERT_TITLE, request.getTitleCode(), request.getTitle(), "职称");
        DictionaryItem hospitalItem = resolveDictionaryItem(DICT_TYPE_EXPERT_HOSPITAL, request.getHospitalCode(), request.getOrganization(), "医院");
        jdbcTemplate.update(
                "UPDATE expert SET expert_name=?, gender=?, birthday=STR_TO_DATE(?, '%Y-%m-%d'), id_card_valid_until=STR_TO_DATE(?, '%Y-%m-%d'), " +
                        "id_card_front_oss_key=?, id_card_back_oss_key=?, phone=?, " +
                        "title_code=?, title=?, hospital_code=?, organization=?, status_reason=?, updated_by=?, updated_at=CURRENT_TIMESTAMP " +
                        "WHERE tenant_id=? AND id=? AND is_deleted=0",
                request.getExpertName(),
                request.getGender(),
                request.getBirthday(),
                request.getIdCardValidUntil(),
                request.getIdCardFrontOssKey(),
                request.getIdCardBackOssKey(),
                request.getPhone(),
                titleItem.getDictCode(),
                titleItem.getDictName(),
                hospitalItem.getDictCode(),
                hospitalItem.getDictName(),
                request.getStatusReason(),
                safeUserId(),
                PLATFORM_TENANT_ID,
                id
        );
        return findById(id);
    }

    public List<ExpertBankCardInfo> listCards(Long expertId) {
        assertExpertExists(expertId);
        assertExpertAccessible(expertId);
        return jdbcTemplate.query(
                "SELECT * FROM expert_bank_card WHERE tenant_id=? AND expert_id=? AND is_deleted=0 ORDER BY is_default DESC, id DESC",
                CARD_ROW_MAPPER,
                PLATFORM_TENANT_ID,
                expertId
        );
    }

    public ExpertBankCardInfo getCard(Long expertId, Long cardId) {
        assertExpertExists(expertId);
        assertExpertAccessible(expertId);
        return findCardById(expertId, cardId);
    }

    @Transactional(rollbackFor = Exception.class)
    public ExpertBankCardInfo addCard(Long expertId, AddBankCardRequest request) {
        assertExpertExists(expertId);
        assertExpertAccessible(expertId);
        if (Boolean.TRUE.equals(request.getIsDefault())) {
            jdbcTemplate.update("UPDATE expert_bank_card SET is_default='N', updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND expert_id=?", PLATFORM_TENANT_ID, expertId);
        }
        jdbcTemplate.update(
                "INSERT INTO expert_bank_card (tenant_id, expert_id, bank_name, bank_province, bank_city, bank_branch_name, bank_card_no, bank_card_front_oss_key, bank_card_back_oss_key, account_name, is_default, card_status, inconsistent_name_approved, change_reason, created_by, updated_by) " +
                        "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                PLATFORM_TENANT_ID,
                expertId,
                request.getBankName(),
                request.getBankProvince(),
                request.getBankCity(),
                request.getBankBranchName(),
                request.getBankCardNo(),
                request.getBankCardFrontOssKey(),
                request.getBankCardBackOssKey(),
                request.getAccountName(),
                Boolean.TRUE.equals(request.getIsDefault()) ? "Y" : "N",
                request.getCardStatus() == null || request.getCardStatus().trim().isEmpty() ? "ENABLED" : request.getCardStatus().trim().toUpperCase(),
                Boolean.TRUE.equals(request.getInconsistentNameApproved()) ? 1 : 0,
                request.getChangeReason(),
                safeUserId(),
                safeUserId()
        );
        Long id = jdbcTemplate.queryForObject("SELECT IFNULL(MAX(id),0) FROM expert_bank_card WHERE tenant_id=? AND expert_id=?", Long.class, PLATFORM_TENANT_ID, expertId);
        List<ExpertBankCardInfo> list = jdbcTemplate.query(
                "SELECT * FROM expert_bank_card WHERE tenant_id=? AND id=? LIMIT 1",
                CARD_ROW_MAPPER,
                PLATFORM_TENANT_ID,
                id == null ? 0L : id
        );
        if (list.isEmpty()) {
            throw new BusinessException(10003, "银行卡不存在");
        }
        return list.get(0);
    }

    @Transactional(rollbackFor = Exception.class)
    public ExpertBankCardInfo updateCard(Long expertId, Long cardId, AddBankCardRequest request) {
        assertExpertExists(expertId);
        assertExpertAccessible(expertId);
        findCardById(expertId, cardId);
        if (Boolean.TRUE.equals(request.getIsDefault())) {
            jdbcTemplate.update(
                    "UPDATE expert_bank_card SET is_default='N', updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND expert_id=?",
                    PLATFORM_TENANT_ID,
                    expertId
            );
        }
        jdbcTemplate.update(
                "UPDATE expert_bank_card SET bank_name=?, bank_province=?, bank_city=?, bank_branch_name=?, bank_card_no=?, bank_card_front_oss_key=?, bank_card_back_oss_key=?, account_name=?, is_default=?, " +
                        "card_status=?, inconsistent_name_approved=?, change_reason=?, updated_by=?, updated_at=CURRENT_TIMESTAMP " +
                        "WHERE tenant_id=? AND expert_id=? AND id=? AND is_deleted=0",
                request.getBankName(),
                request.getBankProvince(),
                request.getBankCity(),
                request.getBankBranchName(),
                request.getBankCardNo(),
                request.getBankCardFrontOssKey(),
                request.getBankCardBackOssKey(),
                request.getAccountName(),
                Boolean.TRUE.equals(request.getIsDefault()) ? "Y" : "N",
                request.getCardStatus() == null || request.getCardStatus().trim().isEmpty() ? "ENABLED" : request.getCardStatus().trim().toUpperCase(),
                Boolean.TRUE.equals(request.getInconsistentNameApproved()) ? 1 : 0,
                request.getChangeReason(),
                safeUserId(),
                PLATFORM_TENANT_ID,
                expertId,
                cardId
        );
        return findCardById(expertId, cardId);
    }

    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> merge(Long targetExpertId, MergeExpertRequest request) {
        assertExpertExists(targetExpertId);
        assertExpertExists(request.getSourceExpertId());
        assertExpertAccessible(targetExpertId);
        assertExpertAccessible(request.getSourceExpertId());
        if (targetExpertId.equals(request.getSourceExpertId())) {
            throw new BusinessException(10001, "来源专家不能与目标专家相同");
        }
        List<ExpertBankCardInfo> sourceCards = jdbcTemplate.query(
                "SELECT * FROM expert_bank_card WHERE tenant_id=? AND expert_id=? AND is_deleted=0",
                CARD_ROW_MAPPER,
                PLATFORM_TENANT_ID,
                request.getSourceExpertId()
        );
        for (ExpertBankCardInfo card : sourceCards) {
            Integer exists = jdbcTemplate.queryForObject(
                    "SELECT COUNT(1) FROM expert_bank_card WHERE tenant_id=? AND expert_id=? AND bank_card_no=? AND is_deleted=0",
                    Integer.class,
                    PLATFORM_TENANT_ID,
                    targetExpertId,
                    card.getBankCardNo()
            );
            if (exists == null || exists == 0) {
                jdbcTemplate.update(
                        "INSERT INTO expert_bank_card (tenant_id, expert_id, bank_name, bank_province, bank_city, bank_branch_name, bank_card_no, bank_card_front_oss_key, bank_card_back_oss_key, account_name, is_default, card_status, inconsistent_name_approved, change_reason, created_by, updated_by) " +
                                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'N', ?, ?, ?, ?, ?)",
                        PLATFORM_TENANT_ID,
                        targetExpertId,
                        card.getBankName(),
                        card.getBankProvince(),
                        card.getBankCity(),
                        card.getBankBranchName(),
                        card.getBankCardNo(),
                        card.getBankCardFrontOssKey(),
                        card.getBankCardBackOssKey(),
                        card.getAccountName(),
                        card.getCardStatus() == null ? "ENABLED" : card.getCardStatus(),
                        Boolean.TRUE.equals(card.getInconsistentNameApproved()) ? 1 : 0,
                        card.getChangeReason(),
                        safeUserId(),
                        safeUserId()
                );
            }
        }
        jdbcTemplate.update(
                "UPDATE expert SET is_deleted=1, updated_by=?, updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND id=?",
                safeUserId(),
                PLATFORM_TENANT_ID,
                request.getSourceExpertId()
        );
        jdbcTemplate.update(
                "INSERT INTO expert_merge_log (tenant_id, target_expert_id, source_expert_id, reason, created_by) VALUES (?, ?, ?, ?, ?)",
                PLATFORM_TENANT_ID,
                targetExpertId,
                request.getSourceExpertId(),
                request.getReason(),
                safeUserId()
        );
        Map<String, Object> data = new LinkedHashMap<String, Object>();
        data.put("targetExpertId", targetExpertId);
        data.put("sourceExpertId", request.getSourceExpertId());
        data.put("status", "MERGED");
        return data;
    }

    public Map<String, Object> export() {
        List<ExpertInfo> list = list(null, 1, 10000).getList();
        Map<String, Object> data = new LinkedHashMap<String, Object>();
        data.put("total", list.size());
        data.put("records", list);
        return data;
    }

    private List<ExpertInfo> filterByExpertScope(List<ExpertInfo> source) {
        DataPermissionService.DataScope scope = dataPermissionService.resolveCurrentUserScope();
        if (scope.isExpertAll()) {
            return source;
        }
        Set<Long> expertIds = new HashSet<Long>();
        for (ExpertInfo item : source) {
            if (item != null && item.getId() != null) {
                expertIds.add(item.getId());
            }
        }
        Map<Long, Long> creatorMap = dataPermissionService.listExpertCreators(expertIds);
        List<ExpertInfo> filtered = new java.util.ArrayList<ExpertInfo>();
        for (ExpertInfo item : source) {
            if (item == null || item.getId() == null) {
                continue;
            }
            if (dataPermissionService.canAccessExpert(item.getId(), creatorMap.get(item.getId()), scope)) {
                filtered.add(item);
            }
        }
        return filtered;
    }

    private void assertExpertAccessible(Long expertId) {
        DataPermissionService.DataScope scope = dataPermissionService.resolveCurrentUserScope();
        Map<Long, Long> creatorMap = dataPermissionService.listExpertCreators(java.util.Collections.singleton(expertId));
        if (!dataPermissionService.canAccessExpert(expertId, creatorMap.get(expertId), scope)) {
            throw new BusinessException(10003, "无权访问该专家");
        }
    }

    private ExpertInfo maskSensitiveFields(ExpertInfo source) {
        return new ExpertInfo(
                source.getId(),
                source.getExpertName(),
                source.getGender(),
                source.getBirthday(),
                maskIdNo(source.getIdNo()),
                source.getIdCardValidUntil(),
                source.getIdCardFrontOssKey(),
                source.getIdCardBackOssKey(),
                maskPhone(source.getPhone()),
                source.getTitleCode(),
                source.getTitle(),
                source.getHospitalCode(),
                source.getOrganization(),
                source.getStatus(),
                source.getStatusReason(),
                source.getStatusChangedAt(),
                source.getExportRestricted()
        );
    }

    private String maskIdNo(String idNo) {
        if (idNo == null || idNo.trim().isEmpty()) {
            return idNo;
        }
        String value = idNo.trim();
        if (value.length() <= 8) {
            return value;
        }
        return value.substring(0, 4) + "********" + value.substring(value.length() - 4);
    }

    private String maskPhone(String phone) {
        if (phone == null || phone.trim().isEmpty()) {
            return phone;
        }
        String value = phone.trim();
        if (value.length() <= 7) {
            return value;
        }
        return value.substring(0, 3) + "****" + value.substring(value.length() - 4);
    }

    private ExpertInfo findById(Long id) {
        List<ExpertInfo> list = jdbcTemplate.query(
                "SELECT e.id, e.expert_name, e.gender, e.birthday, e.id_no, e.id_card_valid_until, e.id_card_front_oss_key, e.id_card_back_oss_key, e.phone, " +
                        "e.title_code, IFNULL(dt.dict_name, e.title) AS title_name, " +
                        "e.hospital_code, IFNULL(dh.dict_name, e.organization) AS hospital_name, " +
                        "e.status, e.status_reason, e.status_changed_at, e.export_restricted " +
                        "FROM expert e " +
                        "LEFT JOIN platform_dictionary_item dt ON dt.dict_type='EXPERT_TITLE' AND dt.dict_code=e.title_code AND dt.is_deleted=0 " +
                        "LEFT JOIN platform_dictionary_item dh ON dh.dict_type='EXPERT_HOSPITAL' AND dh.dict_code=e.hospital_code AND dh.is_deleted=0 " +
                        "WHERE e.tenant_id=? AND e.id=? AND e.is_deleted=0",
                EXPERT_ROW_MAPPER,
                PLATFORM_TENANT_ID,
                id
        );
        if (list.isEmpty()) {
            throw new BusinessException(10003, "专家不存在");
        }
        return list.get(0);
    }

    private void assertExpertExists(Long expertId) {
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM expert WHERE tenant_id=? AND id=? AND is_deleted=0",
                Integer.class,
                PLATFORM_TENANT_ID,
                expertId
        );
        if (count == null || count == 0) {
            throw new BusinessException(10003, "专家不存在");
        }
    }

    private ExpertBankCardInfo findCardById(Long expertId, Long cardId) {
        List<ExpertBankCardInfo> list = jdbcTemplate.query(
                "SELECT * FROM expert_bank_card WHERE tenant_id=? AND expert_id=? AND id=? AND is_deleted=0 LIMIT 1",
                CARD_ROW_MAPPER,
                PLATFORM_TENANT_ID,
                expertId,
                cardId
        );
        if (list.isEmpty()) {
            throw new BusinessException(10003, "银行卡不存在");
        }
        return list.get(0);
    }

    private Long safeUserId() {
        Long userId = AuthContext.userId();
        return userId == null ? 0L : userId;
    }

    private String normalizeContentType(String contentType) {
        if (contentType == null || contentType.trim().isEmpty()) {
            return "application/octet-stream";
        }
        return contentType.trim();
    }

    /**
     * 解析并校验平台字典项：
     * 1) 优先按编码命中；
     * 2) 兼容历史按名称命中；
     * 3) 仅允许启用状态字典项。
     */
    private DictionaryItem resolveDictionaryItem(String dictType, String dictCode, String dictName, String label) {
        String code = dictCode == null ? null : dictCode.trim();
        if (code != null && !code.isEmpty()) {
            List<DictionaryItem> byCode = jdbcTemplate.query(
                    "SELECT dict_code, dict_name FROM platform_dictionary_item " +
                            "WHERE dict_type=? AND dict_code=? AND status='ENABLED' AND is_deleted=0 LIMIT 1",
                    (rs, n) -> new DictionaryItem(rs.getString("dict_code"), rs.getString("dict_name")),
                    dictType,
                    code
            );
            if (byCode.isEmpty()) {
                throw new BusinessException(10001, label + "字典编码不存在或未启用");
            }
            return byCode.get(0);
        }
        String name = dictName == null ? null : dictName.trim();
        if (name == null || name.isEmpty()) {
            throw new BusinessException(10001, label + "不能为空");
        }
        List<DictionaryItem> byName = jdbcTemplate.query(
                "SELECT dict_code, dict_name FROM platform_dictionary_item " +
                        "WHERE dict_type=? AND dict_name=? AND status='ENABLED' AND is_deleted=0 LIMIT 1",
                (rs, n) -> new DictionaryItem(rs.getString("dict_code"), rs.getString("dict_name")),
                dictType,
                name
        );
        if (byName.isEmpty()) {
            throw new BusinessException(10001, label + "不在平台字典内，请先在平台字典中维护");
        }
        return byName.get(0);
    }

    private void validateImportExpert(CreateExpertRequest request, Set<String> batchIdNos, Set<String> batchPhones) {
        if (request == null) {
            throw new BusinessException(10001, "导入行不能为空");
        }
        if (request.getExpertName() == null || request.getExpertName().trim().isEmpty()) {
            throw new BusinessException(10001, "专家姓名不能为空");
        }
        ImportValidationUtils.validateIdNo(request.getIdNo());
        ImportValidationUtils.validatePhone(request.getPhone());
        String idNo = ImportValidationUtils.trim(request.getIdNo()).toUpperCase();
        String phone = ImportValidationUtils.trim(request.getPhone());
        if (!batchIdNos.add(idNo)) {
            throw new BusinessException(10001, "批次内身份证号重复");
        }
        if (!batchPhones.add(phone)) {
            throw new BusinessException(10001, "批次内手机号重复");
        }
    }

    private String buildExpertIdentifier(CreateExpertRequest request) {
        if (request == null) {
            return "";
        }
        String name = request.getExpertName() == null ? "" : request.getExpertName().trim();
        String idNo = request.getIdNo() == null ? "" : request.getIdNo().trim();
        if (!name.isEmpty() && !idNo.isEmpty()) {
            return name + "/" + idNo;
        }
        return !name.isEmpty() ? name : idNo;
    }

    private String resolveImportMessage(Exception ex) {
        if (ex instanceof BusinessException) {
            return ex.getMessage();
        }
        return ex.getMessage() == null || ex.getMessage().trim().isEmpty()
                ? "导入失败"
                : ex.getMessage();
    }

    private static final class DictionaryItem {
        private final String dictCode;
        private final String dictName;

        private DictionaryItem(String dictCode, String dictName) {
            this.dictCode = dictCode;
            this.dictName = dictName;
        }

        private String getDictCode() {
            return dictCode;
        }

        private String getDictName() {
            return dictName;
        }
    }
}
