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.AdamCaomeiPassport;
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.service.caomei.CaomeiBadgeApplyStatus;
import com.liquidnet.service.adam.service.caomei.CaomeiBadgeEligibilityService;
import com.liquidnet.service.adam.service.caomei.CaomeiBadgeGrantService;
import com.liquidnet.service.base.ErrorMapping;
import com.liquidnet.service.base.ResponseDto;
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 AdamRdmService adamRdmService;
    @Autowired
    private CaomeiBadgeGrantService caomeiBadgeGrantService;
    @Autowired
    private CaomeiBadgeEligibilityService caomeiBadgeEligibilityService;

    @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();
            }

            Date grantAt = DateUtil.now();
            List<AdamCaomeiPassportUserBadgeDto> appendVos = caomeiBadgeGrantService.grantBadgesIfAbsent(
                    uid, passportTypeBadges, 1, grantAt);

            // 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<>();
        }
        CaomeiBadgeApplyStatus applyBadgeStatus = caomeiBadgeEligibilityService.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 落库）
        List<AdamCaomeiPassportUserBadgeDto> visaGranted = syncVisaPageBadgesIfEligible(
                uid, published, paidPerformanceIds, applyBadgeStatus);
        if (!visaGranted.isEmpty()) {
            rows.addAll(visaGranted);
            log.info("[getPassportHome] 本次静默发放签证页, uid: {}, badgeIds: {}", uid,
                    visaGranted.stream().map(AdamCaomeiPassportUserBadgeDto::getBadgeId).collect(Collectors.toList()));
        }

        // 首页货架：排除 type=4
        List<AdamCaomeiBadge> shelfPublished = filterShelfPublished(published);
        // 已领徽章 + 货架 type=2 的演出 ID 合并后批量解析名称，避免重复循环查库
        Map<String, String> performanceTitleById = adamRdmService.getPerformanceTitleMapByIds(
                collectPerformanceIdsForTitleLookup(rows, shelfPublished));

        List<AdamCaomeiPassportUserClaimedBadgeVo> userBadgeVos = toClaimedBadgeVos(rows, performanceTitleById);
        List<AdamCaomeiPassportUserClaimedBadgeVo> visaBadges = userBadgeVos.stream()
                .filter(v -> isVisaBadgeType(v.getType()))
                .collect(Collectors.toList());
        home.setVisaBadges(visaBadges);
        List<AdamCaomeiPassportUserClaimedBadgeVo> claimed = userBadgeVos.stream()
                .filter(v -> !isVisaBadgeType(v.getType()))
                .collect(Collectors.toList());
        home.setClaimedBadges(claimed);
        log.info("[getPassportHome] 用户已认领徽章(不含签证页) {}, 签证页 {}, uid: {}",
                claimed.size(), visaBadges.size(), uid);

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

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

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

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

    @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 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());
            String perfId = StringUtils.trimToEmpty(r.getPerformanceId());
            if (needsPerformanceFields(r.getType()) && StringUtils.isNotBlank(perfId)) {
                v.setPerformanceId(perfId);
                String title = performanceTitleById != null ? performanceTitleById.get(perfId) : null;
                v.setPerformanceName(StringUtils.isNotBlank(title) ? title : perfId);
            } else {
                v.setPerformanceId("");
                v.setPerformanceName("");
            }
            v.setClaimedAt(r.getClaimedAt());
            v.setSource(r.getSource());
            return v;
        }).collect(Collectors.toList());
    }

    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));
    }

    /** 收集首页需展示演出名称的场次 ID（已领 type=2/4 + 货架 type=2） */
    private static List<String> collectPerformanceIdsForTitleLookup(List<AdamCaomeiPassportUserBadgeDto> userRows,
                                                                  List<AdamCaomeiBadge> shelfPublished) {
        Set<String> ids = new LinkedHashSet<>();
        if (userRows != null) {
            for (AdamCaomeiPassportUserBadgeDto r : userRows) {
                if (r != null && needsPerformanceFields(r.getType()) && StringUtils.isNotBlank(r.getPerformanceId())) {
                    ids.add(r.getPerformanceId().trim());
                }
            }
        }
        if (shelfPublished != null) {
            for (AdamCaomeiBadge b : shelfPublished) {
                if (b != null && b.getType() != null && b.getType() == 2 && StringUtils.isNotBlank(b.getPerformanceId())) {
                    ids.add(b.getPerformanceId().trim());
                }
            }
        }
        return new ArrayList<>(ids);
    }

    /**
     * home 末尾：对已上架 type=4、用户在该场次已购票或补签审核通过、且尚未获得的签证页执行静默发放。
     * <p>
     * 购票：source=2；仅补签通过（无购票记录）：source=3，与演出纪念徽章认领一致。
     *
     * @param applyBadgeStatus {@link CaomeiBadgeEligibilityService#loadApplyBadgeStatus}，取已通过补签的场次 ID
     * @return 本次实际新增的 user 缓存 DTO（供 home 本地 merge，避免二次读 Redis）
     */
    private List<AdamCaomeiPassportUserBadgeDto> syncVisaPageBadgesIfEligible(String uid,
                                                                              List<AdamCaomeiBadge> published,
                                                                              List<String> paidPerformanceIds,
                                                                              CaomeiBadgeApplyStatus applyBadgeStatus) {
        if (StringUtils.isBlank(uid) || published == null || published.isEmpty()) {
            return Collections.emptyList();
        }

        Set<String> paidSet = paidPerformanceIds == null
                ? Collections.emptySet()
                : paidPerformanceIds.stream().filter(StringUtils::isNotBlank).map(String::trim).collect(Collectors.toSet());
        Set<String> passedApplyPerfSet = applyBadgeStatus == null
                ? Collections.emptySet()
                : applyBadgeStatus.getPassedApplyPerformanceIds();
        if (paidSet.isEmpty() && passedApplyPerfSet.isEmpty()) {
            return Collections.emptyList();
        }

        // 筛选所有 type=4的徽章
        List<AdamCaomeiBadge> visaPublished = published.stream()
                .filter(b -> b != null && isVisaBadgeType(b.getType()))
                .filter(b -> StringUtils.isNotBlank(b.getPerformanceId()))
                .collect(Collectors.toList());
        if (visaPublished.isEmpty()) {
            return Collections.emptyList();
        }

        List<AdamCaomeiBadge> grantByPaid = visaPublished.stream()
                .filter(b -> paidSet.contains(StringUtils.trimToEmpty(b.getPerformanceId())))
                .collect(Collectors.toList());
        List<AdamCaomeiBadge> grantByApplyOnly = visaPublished.stream()
                .filter(b -> {
                    String perfId = StringUtils.trimToEmpty(b.getPerformanceId());
                    return !paidSet.contains(perfId) && passedApplyPerfSet.contains(perfId);
                })
                .collect(Collectors.toList());

        Date grantAt = DateUtil.now();
        List<AdamCaomeiPassportUserBadgeDto> granted = new ArrayList<>();
        if (!grantByPaid.isEmpty()) {
            granted.addAll(caomeiBadgeGrantService.grantBadgesIfAbsent(
                    uid, grantByPaid, USER_BADGE_SOURCE_PAID_AUTO, grantAt));
        }
        if (!grantByApplyOnly.isEmpty()) {
            granted.addAll(caomeiBadgeGrantService.grantBadgesIfAbsent(uid, grantByApplyOnly, 3, grantAt));
        }
        return granted;
    }

    /** type=2 演出纪念、type=4 签证页需展示关联场次 */
    private static boolean needsPerformanceFields(Integer type) {
        return type != null && (type == 2 || type == BADGE_TYPE_VISA);
    }

    /** 首页徽章货架/墙：排除签证页配置 */
    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 List<AdamCaomeiPassportBadgeShelfItemVo> toShelfItems(List<AdamCaomeiBadge> published,
                                                                  Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
                                                                  List<String> paidPerformanceIds,
                                                                  CaomeiBadgeApplyStatus 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 AdamCaomeiPassportBadgeShelfItemVo toShelfItem(AdamCaomeiBadge b,
                                                           Map<String, AdamCaomeiPassportUserBadgeDto> claimedMap,
                                                           List<String> paidPerformanceIds,
                                                           CaomeiBadgeApplyStatus 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);
        }
        AdamCaomeiPassportUserBadgeDto got = claimedMap.get(b.getBadgeId());
        boolean claimed = got != null;
        v.setClaimed(claimed);
        v.setClaimedAt(got != null ? got.getClaimedAt() : null);

        CaomeiBadgeEligibilityService.ShelfInteractState interact =
                caomeiBadgeEligibilityService.resolveShelfInteract(b, claimed, paidPerformanceIds, applyBadgeStatus);
        v.setClaimable(interact.isClaimable());
        v.setApplyPending(interact.isApplyPending());
        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")
        );
    }

}
