package com.liquidnet.service.adam.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.liquidnet.commons.lang.util.CurrentUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.service.adam.dto.AdamCaomeiPassportUserBadgeDto;
import com.liquidnet.service.adam.dto.vo.*;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
import com.liquidnet.service.adam.entity.AdamCaomeiBadgeApplyRecord;
import com.liquidnet.service.adam.entity.AdamCaomeiPassport;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeApplyRecordMapper;
import com.liquidnet.service.adam.mapper.AdamCaomeiPassportMapper;
import com.liquidnet.service.adam.service.AdamRdmService;
import com.liquidnet.service.adam.service.IAdamCaomeiPassportUserService;
import com.liquidnet.service.adam.util.QueueUtils;
import com.liquidnet.service.base.ErrorMapping;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AdamCaomeiPassportUserServiceImpl implements IAdamCaomeiPassportUserService {

    /** 徽章类型：签证页（独立卡片区展示，不出现在首页徽章货架/徽章墙） */
    private static final int BADGE_TYPE_VISA = 4;

    /** 用户获得途径：购票自动发放（签证页在 home 末尾静默发放时使用） */
    private static final int USER_BADGE_SOURCE_PAID_AUTO = 2;

    @Autowired
    private AdamCaomeiPassportMapper adamCaomeiPassportMapper;
    @Autowired
    private AdamCaomeiBadgeApplyRecordMapper badgeApplyRecordMapper;
    @Autowired
    private AdamRdmService adamRdmService;
    @Autowired
    private QueueUtils queueUtils;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseDto<List<AdamCaomeiPassportUserClaimedBadgeVo>> bindPassport(String passportNo) {
        String uid = CurrentUtil.getCurrentUid();

        // 1. 先校验护照信息是否满足绑定条件
        AdamCaomeiPassport passport = adamCaomeiPassportMapper.selectOne(
                Wrappers.lambdaQuery(AdamCaomeiPassport.class)
                        .eq(AdamCaomeiPassport::getPassportNo, passportNo.trim()));

        if (passport == null) {
            log.error("[bindPassport] 未查询到护照信息, passportSn: {}", passportNo);
            return ResponseDto.failure(ErrorMapping.get("10600"));
        }

        Integer st = passport.getStatus();
        if (st != null && st == 2) {
            log.error("[bindPassport] 护照已作废, passportSn: {}", passportNo);
            return ResponseDto.failure(ErrorMapping.get("10601"));
        }

        if (st != null && st == 1) {
            if (uid.equals(StringUtils.trimToEmpty(passport.getUserId()))) {
                return ResponseDto.failure(ErrorMapping.get("10606")); // 提示无需重复绑定
            }
            log.error("[bindPassport] 护照已被其他账号绑定, passportSn: {}", passportNo);
            return ResponseDto.failure(ErrorMapping.get("10602"));
        }

        // 2. 校验当前用户是否已经绑定过其他护照
        AdamCaomeiPassport userExistsPassport = findBoundPassportForUser(uid);
        if (userExistsPassport != null && !passportNo.equals(userExistsPassport.getPassportNo())) {
            log.error("[bindPassport] 已绑定护照, uid: {}, existsPassportSn: {}, newPassportSn: {}", uid, userExistsPassport.getPassportNo(), passportNo);
            return ResponseDto.failure(ErrorMapping.get("10603"));
        }

        // 3. 尝试原子更新绑定状态 (仅当 status = 0 时才能更新成功)
        int updatedRows = adamCaomeiPassportMapper.bindPassportAtomic(passportNo.trim(), uid);

        if (updatedRows > 0) {
            // 4. 优先复用已上架徽章缓存，再筛选护照纪念徽章(type=1)，用于批量发放和返回给前端弹窗
            List<AdamCaomeiBadge> publishedBadges = adamRdmService.getPublishedCaomeiBadges();
            if (publishedBadges == null) {
                publishedBadges = new ArrayList<>();
            }
            // 所有护照类型徽章
            List<AdamCaomeiBadge> passportTypeBadges = publishedBadges.stream()
                    .filter(b -> b.getType() != null && b.getType() == 1)
                    .collect(Collectors.toList());

            if (passportTypeBadges.isEmpty()) {
                log.info("[bindPassport] 暂无上架徽章");
                return ResponseDto.success();
            }

            List<String> badgeIds = passportTypeBadges.stream()
                    .map(AdamCaomeiBadge::getBadgeId)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());

            Date grantAt = DateUtil.now();

            // 兼容 claimBadge 的写法：把本次发放的护照徽章同时写入用户徽章缓存列表
            List<AdamCaomeiPassportUserBadgeDto> cacheBadgeVos = adamRdmService.getUserCaomeiBadgesByUid(uid);
            if (cacheBadgeVos == null) {
                cacheBadgeVos = new ArrayList<>();
            } else {
                cacheBadgeVos = new ArrayList<>(cacheBadgeVos);
            }
            Set<String> existedBadgeIds = cacheBadgeVos.stream()
                    .map(AdamCaomeiPassportUserBadgeDto::getBadgeId)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toSet());
            List<AdamCaomeiPassportUserBadgeDto> appendVos = passportTypeBadges.stream()
                    .filter(b -> b != null && StringUtils.isNotBlank(b.getBadgeId()) && !existedBadgeIds.contains(b.getBadgeId()))
                    .map(b -> {
                        AdamCaomeiPassportUserBadgeDto dto = new AdamCaomeiPassportUserBadgeDto();
                        dto.setBadgeId(b.getBadgeId());
                        dto.setBadgeName(StringUtils.defaultString(b.getName()));
                        dto.setIcon(StringUtils.defaultString(b.getIcon()));
                        dto.setShareText(StringUtils.defaultString(b.getShareText()));
                        dto.setType(b.getType());
                        dto.setClaimedAt(grantAt);
                        dto.setSource(1);
                        dto.setSubTitle(b.getSubTitle());
                        return dto;
                    })
                    .collect(Collectors.toList());
            adamRdmService.addUserCaomeiBadgeDtosByUid(uid, cacheBadgeVos, appendVos);

            LinkedList<Object[]> paramsList = new LinkedList<>();
            for (String badgeId : badgeIds) {
                paramsList.add(new Object[]{uid, badgeId, 1, grantAt});
            }
            queueUtils.sendMsgByRedis(
                    MQConst.AdamQueue.SQL_UCENTER.getKey(),
                    SqlMapping.get("adam_caomei_user_badge.add", paramsList)
            );

            // 5. 返回本次绑定场景下的护照类型徽章列表（用于前端弹窗）
            List<AdamCaomeiPassportUserClaimedBadgeVo> grantedBadges = appendVos.stream().map(b -> {
                AdamCaomeiPassportUserClaimedBadgeVo v = new AdamCaomeiPassportUserClaimedBadgeVo();
                v.setBadgeId(b.getBadgeId());
                v.setName(StringUtils.defaultString(b.getBadgeName()));
                v.setIcon(StringUtils.defaultString(b.getIcon()));
                v.setShareText(StringUtils.defaultString(b.getShareText()));
                v.setType(b.getType());
                v.setClaimedAt(b.getClaimedAt());
                v.setSource(1);
                return v;
            }).collect(Collectors.toList());
            log.info("[bindPassport] 护照绑定成功, uid: {}, passportSn: {}", uid, passportNo);
            return ResponseDto.success(grantedBadges);
        }

        // 6. 如果原子性修改未成功，说明在并发情况下被别人抢先绑定了
        log.error("[bindPassport] 并发绑定失败，护照已被抢占, passportSn: {}", passportNo);
        return ResponseDto.failure(ErrorMapping.get("10602"));
    }

    @Override
    public ResponseDto<AdamCaomeiPassportHomeVo> getPassportHome() {
        String uid = CurrentUtil.getCurrentUid();
        log.info("[getPassportHome] 开始获取护照首页数据, uid: {}", uid);

        // 4. 获取用户当前绑定的护照信息
        AdamCaomeiPassport bound = findBoundPassportForUser(uid);
        if (null == bound) {
            log.error("[getPassportHome] 用户未绑定护照, uid: {}", uid);
            return ResponseDto.failure(ErrorMapping.get("10604")); // 未绑定护照
        }

        // 1. 初始化返回对象
        AdamCaomeiPassportHomeVo home = new AdamCaomeiPassportHomeVo();
        AdamCaomeiPassportUserCardVo card = buildUserCard(uid, bound);
        home.setUserCard(card);

        // 3. 获取用户实名认证信息
        AdamRealInfoVo real = adamRdmService.getRealInfoVoByUidPlain(uid);
        String idCard = null;
        boolean isRealNameVerified = false;
        if (real != null && real.getState() != null && real.getState() == 1 && real.getType() == 1) {
            isRealNameVerified = true;
            idCard = real.getIdCard();
        }
        card.setRealNameVerified(isRealNameVerified);
        log.info("[getPassportHome] 实名认证状态, uid: {}, 是否已实名: {}, 是否有身份证号: {}",
                uid, isRealNameVerified, StringUtils.isNotBlank(idCard));

        // 5. TODO 优化点
        // 身份证号已购买的演出ID
        final List<String> paidPerformanceIds = StringUtils.isNotBlank(idCard)
                ? adamRdmService.getPaidPerformanceIdsByIdCard(idCard)
                : new ArrayList<>();
        log.info("[getPassportHome] 用户已支付的演出订单数量, uid: {}, 数量: {}", uid, paidPerformanceIds.size());

        // 先取上架徽章配置（供补签状态按演出聚合时做 badgeId→performanceId 兜底）
        List<AdamCaomeiBadge> published = adamRdmService.getPublishedCaomeiBadges();
        if (published == null) {
            published = new ArrayList<>();
        }
        ApplyBadgeStatus applyBadgeStatus = loadApplyBadgeStatus(uid, published);

        // 用户已获得记录（含 type=4；展示层再过滤）
        List<AdamCaomeiPassportUserBadgeDto> rows = adamRdmService.getUserCaomeiBadgesByUid(uid);
        if (rows == null) {
            rows = new ArrayList<>();
        } else {
            rows = new ArrayList<>(rows);
        }

        // 末尾：实名且已购对应场次时，静默发放 type=4 签证页（写用户徽章 Redis + MQ 落库）
        boolean visaGranted = syncVisaPageBadgesIfEligible(uid, published, rows, paidPerformanceIds);
        if (visaGranted) {
            List<AdamCaomeiPassportUserBadgeDto> refreshed = adamRdmService.getUserCaomeiBadgesByUid(uid);
            rows = refreshed == null ? new ArrayList<>() : new ArrayList<>(refreshed);
            log.info("[getPassportHome] 本次静默发放签证页后刷新用户徽章缓存, uid: {}", uid);
        }

        Map<String, String> claimedPerformanceTitleById = buildClaimedPerformanceTitleMap(rows);
        // 首页徽章墙：排除 type=4
        List<AdamCaomeiPassportUserClaimedBadgeVo> claimed = toClaimedBadgeVos(rows, claimedPerformanceTitleById).stream()
                .filter(v -> !isVisaBadgeType(v.getType()))
                .collect(Collectors.toList());
        home.setClaimedBadges(claimed);
        log.info("[getPassportHome] 用户已认领的徽章数量(不含签证页), uid: {}, 数量: {}", uid, claimed.size());

        Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap = toClaimedBadgeMap(rows);

        log.info("[getPassportHome] 系统已上架的徽章数量, uid: {}, 数量: {}", uid, published.size());

        // 首页货架：排除 type=4，仅 type=2 需要演出名称
        List<AdamCaomeiBadge> shelfPublished = filterShelfPublished(published);
        Map<String, String> performanceTitleById = buildPerformanceTitleMap(shelfPublished);

        List<AdamCaomeiPassportBadgeShelfItemVo> allBadges = toShelfItems(
                shelfPublished,
                claimedMap,
                paidPerformanceIds,
                applyBadgeStatus,
                performanceTitleById
        );

        home.setAllBadges(allBadges);
        log.info("[getPassportHome] 获取护照首页数据成功, uid: {}", uid);
        return ResponseDto.success(home);
    }

    @Override
    public ResponseDto<List<AdamCaomeiVisaCardVo>> listVisaCards() {
        String uid = CurrentUtil.getCurrentUid();
        AdamCaomeiPassport bound = findBoundPassportForUser(uid);
        if (bound == null) {
            log.error("[listVisaCards] 用户未绑定护照, uid: {}", uid);
            return ResponseDto.failure(ErrorMapping.get("10604"));
        }

        // 仅读用户已获徽章列表（Redis 缓存 miss 时回源 DB）；须先访问 home 触发 type=4 静默发放后才有数据
        List<AdamCaomeiPassportUserBadgeDto> rows = adamRdmService.getUserCaomeiBadgesByUid(uid);
        if (rows == null) {
            rows = Collections.emptyList();
        }
        List<AdamCaomeiPassportUserBadgeDto> visaRows = rows.stream()
                .filter(r -> r != null && isVisaBadgeType(r.getType()))
                .collect(Collectors.toList());
        if (visaRows.isEmpty()) {
            return ResponseDto.success(Collections.emptyList());
        }

        Map<String, String> performanceTitleById = buildPerformanceTitleMapByPerfIds(
                visaRows.stream()
                        .map(AdamCaomeiPassportUserBadgeDto::getPerformanceId)
                        .filter(StringUtils::isNotBlank)
                        .distinct()
                        .collect(Collectors.toList())
        );

        List<AdamCaomeiVisaCardVo> cards = visaRows.stream()
                .map(r -> toVisaCardVoFromUserBadge(r, performanceTitleById))
                .collect(Collectors.toList());
        return ResponseDto.success(cards);
    }

    @Override
    public ResponseDto<Boolean> checkPassportBound() {
        String uid = CurrentUtil.getCurrentUid();
        AdamCaomeiPassport bound = findBoundPassportForUser(uid);
        return ResponseDto.success(bound != null);
    }

    private AdamCaomeiPassportUserCardVo buildUserCard(String uid, AdamCaomeiPassport bound) {
        AdamCaomeiPassportUserCardVo card = new AdamCaomeiPassportUserCardVo();
        card.setPassportBound(true);
        card.setPassportNo(bound.getPassportNo());
        card.setPassportClaimedAt(bound.getBoundAt());

        AdamUserInfoVo userInfo = adamRdmService.getUserInfoVoByUid(uid);
        if (userInfo != null) {
            card.setAvatar(StringUtils.defaultString(userInfo.getAvatar()));
            card.setNickname(StringUtils.defaultString(userInfo.getNickname()));
        } else {
            card.setAvatar("");
            card.setNickname("");
        }
        return card;
    }

    private ApplyBadgeStatus loadApplyBadgeStatus(String uid, List<AdamCaomeiBadge> published) {
        Map<String, String> badgeIdToPerformanceId = new HashMap<>();
        if (published != null) {
            for (AdamCaomeiBadge b : published) {
                if (b != null && StringUtils.isNotBlank(b.getBadgeId())) {
                    badgeIdToPerformanceId.put(b.getBadgeId(), StringUtils.trimToEmpty(b.getPerformanceId()));
                }
            }
        }
        List<AdamCaomeiBadgeApplyRecord> applyRecords = badgeApplyRecordMapper.selectList(
                Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
                        .eq(AdamCaomeiBadgeApplyRecord::getUserId, uid)
                        .in(AdamCaomeiBadgeApplyRecord::getAuditStatus, 0, 1)
        );
        Set<String> passedApplyBadgeIds = new HashSet<>();
        Set<String> pendingApplyBadgeIds = new HashSet<>();
        Set<String> passedApplyPerformanceIds = new HashSet<>();
        Set<String> pendingApplyPerformanceIds = new HashSet<>();
        for (AdamCaomeiBadgeApplyRecord r : applyRecords) {
            if (r == null || StringUtils.isBlank(r.getBadgeId()) || r.getAuditStatus() == null) {
                continue;
            }
            String perf = StringUtils.trimToEmpty(r.getPerformanceId());
            if (StringUtils.isBlank(perf)) {
                perf = StringUtils.trimToEmpty(badgeIdToPerformanceId.get(r.getBadgeId()));
            }
            if (r.getAuditStatus() == 1) {
                passedApplyBadgeIds.add(r.getBadgeId());
                if (StringUtils.isNotBlank(perf)) {
                    passedApplyPerformanceIds.add(perf);
                }
            } else if (r.getAuditStatus() == 0) {
                pendingApplyBadgeIds.add(r.getBadgeId());
                if (StringUtils.isNotBlank(perf)) {
                    pendingApplyPerformanceIds.add(perf);
                }
            }
        }
        return new ApplyBadgeStatus(
                passedApplyBadgeIds,
                pendingApplyBadgeIds,
                passedApplyPerformanceIds,
                pendingApplyPerformanceIds
        );
    }

    private static List<AdamCaomeiPassportUserClaimedBadgeVo> toClaimedBadgeVos(List<AdamCaomeiPassportUserBadgeDto> rows,
                                                                               Map<String, String> performanceTitleById) {
        return rows.stream().map(r -> {
            AdamCaomeiPassportUserClaimedBadgeVo v = new AdamCaomeiPassportUserClaimedBadgeVo();
            v.setBadgeId(r.getBadgeId());
            v.setName(StringUtils.defaultString(r.getBadgeName()));
            v.setSubTitle(StringUtils.defaultString(r.getSubTitle()));
            v.setIcon(StringUtils.defaultString(r.getIcon()));
            v.setShareText(StringUtils.defaultString(r.getShareText()));
            v.setType(r.getType());
            if (r.getType() != null && r.getType() == 2 && StringUtils.isNotBlank(r.getPerformanceId())) {
                String title = performanceTitleById != null ? performanceTitleById.get(r.getPerformanceId()) : null;
                v.setPerformanceName(StringUtils.isNotBlank(title) ? title : r.getPerformanceId());
            } else {
                v.setPerformanceName("");
            }
            v.setClaimedAt(r.getClaimedAt());
            v.setSource(r.getSource());
            return v;
        }).collect(Collectors.toList());
    }

    private Map<String, String> buildClaimedPerformanceTitleMap(List<AdamCaomeiPassportUserBadgeDto> claimedRows) {
        List<String> perfIds = claimedRows.stream()
                .filter(r -> r != null && r.getType() != null && r.getType() == 2)
                .map(AdamCaomeiPassportUserBadgeDto::getPerformanceId)
                .filter(StringUtils::isNotBlank)
                .distinct()
                .collect(Collectors.toList());
        if (perfIds.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, String> map = new HashMap<>(perfIds.size() * 2);
        for (String perfId : perfIds) {
            String title = adamRdmService.getPerformanceTitleById(perfId);
            if (StringUtils.isNotBlank(title)) {
                map.put(perfId, title);
            }
        }
        return map;
    }

    private static Map<String, AdamCaomeiPassportUserBadgeDto> toClaimedBadgeMap(List<AdamCaomeiPassportUserBadgeDto> rows) {
        return rows.stream()
                .filter(r -> StringUtils.isNotBlank(r.getBadgeId()))
                .collect(Collectors.toMap(AdamCaomeiPassportUserBadgeDto::getBadgeId, Function.identity(), (a, b) -> a));
    }

    private Map<String, String> buildPerformanceTitleMap(List<AdamCaomeiBadge> published) {
        List<String> perfIds = published.stream()
                .filter(b -> b != null && b.getType() != null && b.getType() == 2)
                .map(AdamCaomeiBadge::getPerformanceId)
                .filter(StringUtils::isNotBlank)
                .distinct()
                .collect(Collectors.toList());
        if (perfIds.isEmpty()) {
            return Collections.emptyMap();
        }

        Map<String, String> map = new HashMap<>(perfIds.size() * 2);
        for (String perfId : perfIds) {
            String title = adamRdmService.getPerformanceTitleById(perfId);
            if (StringUtils.isNotBlank(title)) {
                map.put(perfId, title);
            }
        }
        return map;
    }

    /**
     * 按演出 ID 批量解析名称（Kylin 演出缓存），供签证卡片等展示。
     */
    private Map<String, String> buildPerformanceTitleMapByPerfIds(List<String> perfIds) {
        if (perfIds == null || perfIds.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, String> map = new HashMap<>(perfIds.size() * 2);
        for (String perfId : perfIds) {
            if (StringUtils.isBlank(perfId)) {
                continue;
            }
            String title = adamRdmService.getPerformanceTitleById(perfId);
            if (StringUtils.isNotBlank(title)) {
                map.put(perfId, title);
            }
        }
        return map;
    }

    /**
     * 将用户已获徽章 DTO 转为签证页卡片 VO（仅已发放记录调用）。
     */
    private static AdamCaomeiVisaCardVo toVisaCardVoFromUserBadge(AdamCaomeiPassportUserBadgeDto r,
                                                                   Map<String, String> performanceTitleById) {
        AdamCaomeiVisaCardVo vo = new AdamCaomeiVisaCardVo();
        vo.setBadgeId(r.getBadgeId());
        vo.setName(StringUtils.defaultString(r.getBadgeName()));
        vo.setSubTitle(StringUtils.defaultString(r.getSubTitle()));
        vo.setIcon(StringUtils.defaultString(r.getIcon()));
        vo.setShareText(StringUtils.defaultString(r.getShareText()));
        String perfId = StringUtils.trimToEmpty(r.getPerformanceId());
        vo.setPerformanceId(perfId);
        String title = performanceTitleById != null ? performanceTitleById.get(perfId) : null;
        vo.setPerformanceName(StringUtils.isNotBlank(title) ? title : perfId);
        vo.setClaimed(true);
        vo.setClaimedAt(r.getClaimedAt());
        return vo;
    }

    /**
     * home 末尾：对已上架 type=4、用户已购对应场次且尚未获得的签证页执行静默发放。
     *
     * @return 是否本次有发放（用于刷新用户徽章缓存后再组装首页响应）
     */
    private boolean syncVisaPageBadgesIfEligible(String uid,
                                                 List<AdamCaomeiBadge> published,
                                                 List<AdamCaomeiPassportUserBadgeDto> badgeVos,
                                                 List<String> paidPerformanceIds) {
        if (StringUtils.isBlank(uid) || published == null || published.isEmpty()) {
            return false;
        }
        if (paidPerformanceIds == null || paidPerformanceIds.isEmpty()) {
            return false;
        }
        Set<String> paidSet = new HashSet<>(paidPerformanceIds);
        Set<String> claimedBadgeIds = badgeVos.stream()
                .map(AdamCaomeiPassportUserBadgeDto::getBadgeId)
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.toSet());

        List<AdamCaomeiBadge> toGrant = published.stream()
                .filter(b -> b != null && isVisaBadgeType(b.getType()))
                .filter(b -> StringUtils.isNotBlank(b.getPerformanceId()))
                .filter(b -> paidSet.contains(StringUtils.trimToEmpty(b.getPerformanceId())))
                .filter(b -> StringUtils.isNotBlank(b.getBadgeId()) && !claimedBadgeIds.contains(b.getBadgeId()))
                .collect(Collectors.toList());
        if (toGrant.isEmpty()) {
            return false;
        }

        Date grantAt = DateUtil.now();
        List<AdamCaomeiPassportUserBadgeDto> appendVos = new ArrayList<>(toGrant.size());
        for (AdamCaomeiBadge badge : toGrant) {
            AdamCaomeiPassportUserBadgeDto dto = new AdamCaomeiPassportUserBadgeDto();
            dto.setBadgeId(badge.getBadgeId());
            dto.setBadgeName(StringUtils.defaultString(badge.getName()));
            dto.setSubTitle(StringUtils.defaultString(badge.getSubTitle()));
            dto.setIcon(StringUtils.defaultString(badge.getIcon()));
            dto.setShareText(StringUtils.defaultString(badge.getShareText()));
            dto.setType(badge.getType());
            dto.setPerformanceId(StringUtils.trimToEmpty(badge.getPerformanceId()));
            dto.setClaimedAt(grantAt);
            dto.setSource(USER_BADGE_SOURCE_PAID_AUTO);
            appendVos.add(dto);
        }
        adamRdmService.addUserCaomeiBadgeDtosByUid(uid, badgeVos, appendVos);

        LinkedList<Object[]> paramsList = new LinkedList<>();
        for (AdamCaomeiBadge badge : toGrant) {
            paramsList.add(new Object[]{uid, badge.getBadgeId(), USER_BADGE_SOURCE_PAID_AUTO, grantAt});
        }
        queueUtils.sendMsgByRedis(
                MQConst.AdamQueue.SQL_UCENTER.getKey(),
                SqlMapping.get("adam_caomei_user_badge.add", paramsList)
        );
        log.info("[syncVisaPageBadgesIfEligible] 静默发放签证页, uid: {}, badgeIds: {}",
                uid, toGrant.stream().map(AdamCaomeiBadge::getBadgeId).collect(Collectors.toList()));
        return true;
    }

    /** 首页徽章货架/墙：排除签证页配置 */
    private static List<AdamCaomeiBadge> filterShelfPublished(List<AdamCaomeiBadge> published) {
        return published.stream()
                .filter(b -> b != null && !isVisaBadgeType(b.getType()))
                .collect(Collectors.toList());
    }

    private static boolean isVisaBadgeType(Integer type) {
        return type != null && type == BADGE_TYPE_VISA;
    }

    private static List<AdamCaomeiPassportBadgeShelfItemVo> toShelfItems(List<AdamCaomeiBadge> published,
                                                                          Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
                                                                          List<String> paidPerformanceIds,
                                                                          ApplyBadgeStatus applyBadgeStatus,
                                                                          Map<String, String> performanceTitleById) {
        return published.stream()
                .map(b -> toShelfItem(b, claimedMap, paidPerformanceIds, applyBadgeStatus, performanceTitleById))
                .collect(Collectors.toList());
    }

    /**
     * @param b                  徽章信息
     * @param claimedMap         用户已领取的徽章
     * @param paidPerformanceIds 当前账号实名身份证号码购买的演出IDs
     * @param applyBadgeStatus   补签审核状态（按徽章 + 按同场次演出聚合）
     * @param performanceTitleById 演出 ID → 演出名称（仅 type=2 使用）
     * @return
     */
    private static AdamCaomeiPassportBadgeShelfItemVo toShelfItem(AdamCaomeiBadge b,
                                                                  Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
                                                                  List<String> paidPerformanceIds,
                                                                  ApplyBadgeStatus applyBadgeStatus,
                                                                  Map<String, String> performanceTitleById) {
        AdamCaomeiPassportBadgeShelfItemVo v = new AdamCaomeiPassportBadgeShelfItemVo();
        v.setBadgeId(b.getBadgeId());
        v.setName(StringUtils.defaultString(b.getName()));
        v.setSubTitle(StringUtils.defaultString(b.getSubTitle()));
        v.setIcon(StringUtils.defaultString(b.getIcon()));
        v.setShareText(StringUtils.defaultString(b.getShareText()));
        v.setType(b.getType());
        String perfId = StringUtils.defaultString(b.getPerformanceId());
        v.setPerformanceId(perfId);
        int type = b.getType() == null ? 0 : b.getType();
        if (type == 2 && StringUtils.isNotBlank(perfId)) {
            String title = performanceTitleById != null ? performanceTitleById.get(perfId) : null;
            v.setPerformanceName(StringUtils.isNotBlank(title) ? title : perfId);
        }
        v.setApplyPending(false);

        // 判断当前徽章是否已认领
        AdamCaomeiPassportUserBadgeDto got = claimedMap.get(b.getBadgeId());
        boolean claimed = got != null;
        v.setClaimed(claimed);
        v.setClaimedAt(got != null ? got.getClaimedAt() : null);

        // 针对未认领的徽章，根据类型判断是否可认领 (claimable)
        if (!claimed) {
            if (type == 1) {
                // 护照纪念徽章：只要绑定了护照，就可认领（通常是绑定时漏发或后来新上架的）
                v.setClaimable(true);
            } else if (type == 2) {
                // 演出纪念徽章：有购票记录，或本场次任一补签审核通过，则本场次关联徽章均可认领
                boolean canClaimByPaid = paidPerformanceIds != null && paidPerformanceIds.contains(b.getPerformanceId());
                boolean canClaimByApplyThisBadge = applyBadgeStatus.getPassedApplyBadgeIds().contains(b.getBadgeId());
                boolean canClaimByApplyThisPerf = StringUtils.isNotBlank(perfId)
                        && applyBadgeStatus.getPassedApplyPerformanceIds().contains(perfId);
                v.setClaimable(canClaimByPaid || canClaimByApplyThisBadge || canClaimByApplyThisPerf);
                boolean pendingThisBadge = applyBadgeStatus.getPendingApplyBadgeIds().contains(b.getBadgeId());
                boolean pendingThisPerf = StringUtils.isNotBlank(perfId)
                        && applyBadgeStatus.getPendingApplyPerformanceIds().contains(perfId);
                v.setApplyPending(pendingThisBadge || pendingThisPerf);
            } else if (type == 3) {
                // 特殊徽章：不可自助领取，需要提示用户
                v.setClaimable(false);
            } else if (type == BADGE_TYPE_VISA) {
                // 签证页不在首页货架展示；若配置误入货架列表，不可点击认领（由 home 静默发放）
                v.setClaimable(false);
            }
        } else {
            // 已认领的徽章，可认领状态置为 false
            v.setClaimable(false);
        }
        return v;
    }

    private AdamCaomeiPassport findBoundPassportForUser(String uid) {
        if (StringUtils.isBlank(uid)) {
            return null;
        }
        return adamCaomeiPassportMapper.selectOne(
                Wrappers.lambdaQuery(AdamCaomeiPassport.class)
                        .eq(AdamCaomeiPassport::getUserId, uid)
                        .eq(AdamCaomeiPassport::getStatus, 1)
                        .last("limit 1")
        );
    }

    private static final class ApplyBadgeStatus {
        private final Set<String> passedApplyBadgeIds;
        private final Set<String> pendingApplyBadgeIds;
        private final Set<String> passedApplyPerformanceIds;
        private final Set<String> pendingApplyPerformanceIds;

        private ApplyBadgeStatus(Set<String> passedApplyBadgeIds,
                                 Set<String> pendingApplyBadgeIds,
                                 Set<String> passedApplyPerformanceIds,
                                 Set<String> pendingApplyPerformanceIds) {
            this.passedApplyBadgeIds = passedApplyBadgeIds;
            this.pendingApplyBadgeIds = pendingApplyBadgeIds;
            this.passedApplyPerformanceIds = passedApplyPerformanceIds;
            this.pendingApplyPerformanceIds = pendingApplyPerformanceIds;
        }

        Set<String> getPassedApplyBadgeIds() {
            return passedApplyBadgeIds;
        }

        Set<String> getPendingApplyBadgeIds() {
            return pendingApplyBadgeIds;
        }

        Set<String> getPassedApplyPerformanceIds() {
            return passedApplyPerformanceIds;
        }

        Set<String> getPendingApplyPerformanceIds() {
            return pendingApplyPerformanceIds;
        }
    }
}
