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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.service.adam.dto.AdamCaomeiPassportUserBadgeDto;
import com.liquidnet.service.adam.dto.param.AdamCaomeiBadgeApplyParam;
import com.liquidnet.service.adam.dto.AdamCaomeiBadgeApplyRecordUserDto;
import com.liquidnet.service.adam.dto.vo.AdamCaomeiBadgeApplyRecordUserVo;
import com.liquidnet.service.adam.dto.vo.AdamRealInfoVo;
import com.liquidnet.service.adam.entity.AdamCaomeiBadgeApplyRecord;
import com.liquidnet.service.adam.entity.AdamCaomeiBadge;
import com.liquidnet.service.adam.entity.AdamCaomeiPassport;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeApplyRecordMapper;
import com.liquidnet.service.adam.mapper.AdamCaomeiBadgeMapper;
import com.liquidnet.service.adam.mapper.AdamCaomeiPassportMapper;
import com.liquidnet.service.adam.service.AdamRdmService;
import com.liquidnet.service.adam.service.IAdamCaomeiBadgeUserService;
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.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AdamCaomeiBadgeUserServiceImpl implements IAdamCaomeiBadgeUserService {

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

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseDto<List<String>> claimBadges(List<String> badgeIds, String uid) {
        if (StringUtils.isBlank(uid)) {
            return ResponseDto.failure(ErrorMapping.get("10001"));
        }
        if (badgeIds == null || badgeIds.isEmpty()) {
            return ResponseDto.failure("徽章ID列表不能为空");
        }
        List<String> requestBadgeIds = badgeIds.stream()
                .filter(StringUtils::isNotBlank)
                .map(StringUtils::trim)
                .collect(Collectors.collectingAndThen(Collectors.toCollection(LinkedHashSet::new), ArrayList::new));
        if (requestBadgeIds.isEmpty()) {
            return ResponseDto.failure("徽章ID列表不能为空");
        }

        // 1) 批量查询徽章配置（一次查询）
        List<AdamCaomeiBadge> badges = adamCaomeiBadgeMapper.selectList(
                Wrappers.lambdaQuery(AdamCaomeiBadge.class)
                        .in(AdamCaomeiBadge::getBadgeId, requestBadgeIds)
                        .eq(AdamCaomeiBadge::getDisplayStatus, 1)
        );
        if (badges == null || badges.size() != requestBadgeIds.size()) {
            log.error("[claimBadges] 存在徽章不存在或未上架, uid: {}, badgeIds: {}", uid, requestBadgeIds);
            return ResponseDto.failure(ErrorMapping.get("10607"));
        }
        Map<String, AdamCaomeiBadge> badgeMap = badges.stream()
                .collect(Collectors.toMap(AdamCaomeiBadge::getBadgeId, b -> b));
        List<AdamCaomeiBadge> orderedBadges = requestBadgeIds.stream()
                .map(badgeMap::get)
                .collect(Collectors.toList());

        // 2. 与收货地址 add 一致：先取 Redis（或回源）用户徽章列表，再判重；DB 再兜一层防缓存未命中或其它写入路径
        List<AdamCaomeiPassportUserBadgeDto> badgeVos = adamRdmService.getUserCaomeiBadgesByUid(uid);
        if (badgeVos == null) {
            badgeVos = new ArrayList<>();
        } else {
            badgeVos = new ArrayList<>(badgeVos);
        }
        Set<String> claimedInCache = badgeVos.stream()
                .map(AdamCaomeiPassportUserBadgeDto::getBadgeId)
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.toSet());
        if (requestBadgeIds.stream().anyMatch(claimedInCache::contains)) {
            log.info("[claimBadges] 用户已领取过徽章(Redis列表), uid: {}, badgeIds: {}", uid, requestBadgeIds);
            return ResponseDto.failure(ErrorMapping.get("10608"));
        }
        List<String> claimedInDb = adamCaomeiBadgeMapper.selectClaimedBadgeIdsByUserAndBadgeIds(uid, requestBadgeIds);
        if (claimedInDb != null && !claimedInDb.isEmpty()) {
            log.info("[claimBadges] 用户已领取过徽章(DB), uid: {}, badgeIds: {}", uid, claimedInDb);
            return ResponseDto.failure(ErrorMapping.get("10608"));
        }

        boolean hasType1 = orderedBadges.stream().anyMatch(b -> b.getType() != null && b.getType() == 1);
        boolean hasType2 = orderedBadges.stream().anyMatch(b -> b.getType() != null && b.getType() == 2);
        boolean hasType3 = orderedBadges.stream().anyMatch(b -> b.getType() != null && b.getType() == 3);
        boolean hasUnknownType = orderedBadges.stream().anyMatch(b -> b.getType() == null || (b.getType() != 1 && b.getType() != 2 && b.getType() != 3));

        if (hasType3) {
            log.error("[claimBadges] 含特殊徽章不可自助领取, uid: {}, badgeIds: {}", uid, requestBadgeIds);
            return ResponseDto.failure(ErrorMapping.get("10612"));
        }
        if (hasUnknownType) {
            log.error("[claimBadges] 含未知徽章类型, uid: {}, badgeIds: {}", uid, requestBadgeIds);
            return ResponseDto.failure(ErrorMapping.get("10613"));
        }

        if (hasType1) {
            AdamCaomeiPassport bound = adamCaomeiPassportMapper.selectOne(
                    Wrappers.lambdaQuery(AdamCaomeiPassport.class)
                            .eq(AdamCaomeiPassport::getUserId, uid)
                            .eq(AdamCaomeiPassport::getStatus, 1)
                            .last("limit 1")
            );
            if (bound == null) {
                log.error("[claimBadges] 认领护照纪念徽章需先绑定护照, uid: {}, badgeIds: {}", uid, requestBadgeIds);
                return ResponseDto.failure(ErrorMapping.get("10609"));
            }
        }

        Set<String> paidPerformanceSet = Collections.emptySet();
        Set<String> passedApplyBadgeIds = Collections.emptySet();
        Set<String> passedApplyPerformanceIds = Collections.emptySet();
        Map<String, Set<String>> perfAllBadgeIds = Collections.emptyMap();
        if (hasType2) {
            AdamRealInfoVo real = adamRdmService.getRealInfoVoByUidPlain(uid);
            if (real == null || real.getState() == null || real.getState() != 1 || StringUtils.isBlank(real.getIdCard())) {
                log.error("[claimBadges] 认领演出徽章需先实名, uid: {}, badgeIds: {}", uid, requestBadgeIds);
                return ResponseDto.failure(ErrorMapping.get("10610"));
            }
            List<String> paidPerformanceIds = adamRdmService.getPaidPerformanceIdsByIdCard(real.getIdCard());
            paidPerformanceSet = paidPerformanceIds == null ? Collections.emptySet() : new HashSet<>(paidPerformanceIds);

            List<AdamCaomeiBadgeApplyRecord> passedApplyRecords = badgeApplyRecordMapper.selectList(
                    Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
                            .eq(AdamCaomeiBadgeApplyRecord::getUserId, uid)
                            .eq(AdamCaomeiBadgeApplyRecord::getAuditStatus, 1)
            );
            passedApplyBadgeIds = new HashSet<>();
            passedApplyPerformanceIds = new HashSet<>();
            if (passedApplyRecords != null) {
                for (AdamCaomeiBadgeApplyRecord r : passedApplyRecords) {
                    if (r == null) {
                        continue;
                    }
                    if (StringUtils.isNotBlank(r.getBadgeId())) {
                        passedApplyBadgeIds.add(r.getBadgeId());
                    }
                    if (StringUtils.isNotBlank(r.getPerformanceId())) {
                        passedApplyPerformanceIds.add(r.getPerformanceId());
                    }
                }
            }

            Set<String> targetPerfIds = orderedBadges.stream()
                    .filter(b -> b.getType() != null && b.getType() == 2)
                    .map(AdamCaomeiBadge::getPerformanceId)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toSet());
            if (!targetPerfIds.isEmpty()) {
                List<AdamCaomeiBadge> perfBadges = adamCaomeiBadgeMapper.selectList(
                        Wrappers.lambdaQuery(AdamCaomeiBadge.class)
                                .eq(AdamCaomeiBadge::getType, 2)
                                .in(AdamCaomeiBadge::getPerformanceId, targetPerfIds)
                );
                perfAllBadgeIds = perfBadges.stream()
                        .filter(b -> StringUtils.isNotBlank(b.getPerformanceId()) && StringUtils.isNotBlank(b.getBadgeId()))
                        .collect(Collectors.groupingBy(AdamCaomeiBadge::getPerformanceId,
                                Collectors.mapping(AdamCaomeiBadge::getBadgeId, Collectors.toSet())));
            }
        }

        List<String> claimedBadgeIds = new ArrayList<>();
        for (AdamCaomeiBadge badge : orderedBadges) {
            int type = badge.getType() == null ? 0 : badge.getType();
            int source = 1;
            if (type == 2) {
                String perfId = StringUtils.trimToEmpty(badge.getPerformanceId());
                boolean hasPaidRecord = StringUtils.isNotBlank(perfId) && paidPerformanceSet.contains(perfId);
                boolean hasPassedApply = false;
                if (StringUtils.isNotBlank(perfId)) {
                    if (passedApplyPerformanceIds.contains(perfId)) {
                        hasPassedApply = true;
                    } else {
                        Set<String> badgeIdSet = perfAllBadgeIds.getOrDefault(perfId, Collections.emptySet());
                        hasPassedApply = badgeIdSet.stream().anyMatch(passedApplyBadgeIds::contains);
                    }
                }
                if (!hasPaidRecord && !hasPassedApply) {
                    log.error("[claimBadges] 无购票记录且无通过补签，无法认领, uid: {}, badgeId: {}", uid, badge.getBadgeId());
                    return ResponseDto.failure(ErrorMapping.get("10611"));
                }
                source = hasPaidRecord ? 2 : 3;
            }

            grantUserBadgeRedisThenMq(uid, badge, source, badgeVos);
            claimedBadgeIds.add(badge.getBadgeId());
        }

        log.info("[claimBadges] 批量认领成功, uid: {}, badgeIds: {}", uid, claimedBadgeIds);
        return ResponseDto.success(claimedBadgeIds);
    }

    /**
     * Redis 追加用户徽章展示 DTO，再发 MQ 异步执行 sqlmap 中的 INSERT。
     */
    private void grantUserBadgeRedisThenMq(String uid, AdamCaomeiBadge badge, int source,
                                           List<AdamCaomeiPassportUserBadgeDto> badgeVos) {
        Date now = new Date();
        AdamCaomeiPassportUserBadgeDto dto = new AdamCaomeiPassportUserBadgeDto();
        dto.setBadgeId(badge.getBadgeId());
        dto.setBadgeName(StringUtils.defaultString(badge.getName()));
        dto.setIcon(StringUtils.defaultString(badge.getIcon()));
        dto.setShareText(StringUtils.defaultString(badge.getShareText()));
        dto.setType(badge.getType());
        dto.setClaimedAt(now);
        dto.setSource(source);

        adamRdmService.addUserCaomeiBadgeDtoByUid(uid, badgeVos, dto);

        long t = System.currentTimeMillis();
        queueUtils.sendMsgByRedis(
                MQConst.AdamQueue.SQL_UCENTER.getKey(),
                SqlMapping.get("adam_caomei_user_badge.add", uid, badge.getBadgeId(), source, now)
        );
        log.debug("[claimBadge] MQ耗时:{}ms, uid: {}, badgeId: {}", System.currentTimeMillis() - t, uid, badge.getBadgeId());
    }

    @Override
    public ResponseDto<List<AdamCaomeiBadgeApplyRecordUserVo>> getApplyRecords(String uid) {
        if (StringUtils.isBlank(uid)) {
            return ResponseDto.failure(ErrorMapping.get("10001"));
        }
        List<AdamCaomeiBadgeApplyRecordUserDto> rows = badgeApplyRecordMapper.selectUserApplyRecordsByUid(uid);
        if (rows == null) {
            rows = Collections.emptyList();
        }
        List<AdamCaomeiBadgeApplyRecordUserVo> list = rows.stream().map(r -> {
            AdamCaomeiBadgeApplyRecordUserVo v = new AdamCaomeiBadgeApplyRecordUserVo();
            v.setApplyRecordId(r.getApplyRecordId());
            v.setBadgeName(StringUtils.defaultString(r.getBadgeName()));
            v.setApplyTime(r.getApplyTime());
            v.setAuditStatus(r.getAuditStatus());
            v.setRejectReason(StringUtils.defaultString(r.getRejectReason()));
            v.setCanReupload(r.getAuditStatus() != null && r.getAuditStatus() == 2);
            v.setProofImageUrl(r.getProofImageUrl());
            return v;
        }).collect(Collectors.toList());
        return ResponseDto.success(list);
    }

    @Override
    public ResponseDto<String> applyBadge(AdamCaomeiBadgeApplyParam param, String uid) {
        if (StringUtils.isBlank(uid)) {
            return ResponseDto.failure(ErrorMapping.get("10001"));
        }
        if (param == null || StringUtils.isBlank(param.getBadgeId()) || StringUtils.isBlank(param.getProofImageUrl())) {
            return ResponseDto.failure("参数错误");
        }
        String badgeId = StringUtils.trimToEmpty(param.getBadgeId());
        String proofImageUrl = StringUtils.trimToEmpty(param.getProofImageUrl());

        AdamCaomeiBadge badge = adamCaomeiBadgeMapper.selectOne(
                Wrappers.lambdaQuery(AdamCaomeiBadge.class)
                        .eq(AdamCaomeiBadge::getBadgeId, badgeId)
                        .last("limit 1")
        );
        if (badge == null) {
            return ResponseDto.failure(ErrorMapping.get("10607"));
        }
        if (badge.getType() == null || badge.getType() != 2) {
            return ResponseDto.failure("仅演出纪念徽章支持补签申请");
        }

        int claimedCount = adamCaomeiBadgeMapper.checkUserBadgeExists(uid, badgeId);
        if (claimedCount > 0) {
            return ResponseDto.failure(ErrorMapping.get("10608"));
        }

        String performanceId = StringUtils.trimToEmpty(badge.getPerformanceId());
        int pendingApply;
        if (StringUtils.isNotBlank(performanceId)) {
            pendingApply = countApplyForUserPerformanceAudit(uid, performanceId, 0);
        } else {
            Integer pendingCount = badgeApplyRecordMapper.selectCount(
                    Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
                            .eq(AdamCaomeiBadgeApplyRecord::getUserId, uid)
                            .eq(AdamCaomeiBadgeApplyRecord::getBadgeId, badgeId)
                            .eq(AdamCaomeiBadgeApplyRecord::getAuditStatus, 0)
            );
            pendingApply = pendingCount == null ? 0 : pendingCount;
        }
        if (pendingApply > 0) {
            return ResponseDto.failure("该场次已有待审核的补签申请，请勿重复提交");
        }

        final String applyRecordId = IDGenerator.nextSnowId();
        long t = System.currentTimeMillis();
        boolean autoPass = isPerformanceOngoing(performanceId);
        if (autoPass) {
            // 与 admin 审核通过保持一致：仅将申请记录标记为已通过，不自动发放徽章
            queueUtils.sendMsgByRedis(
                    MQConst.AdamQueue.SQL_UCENTER.getKey(),
                    SqlMapping.get("adam_caomei_badge_apply_record.add_passed",
                            applyRecordId,
                            uid,
                            badgeId,
                            performanceId,
                            proofImageUrl)
            );
        } else {
            queueUtils.sendMsgByRedis(
                    MQConst.AdamQueue.SQL_UCENTER.getKey(),
                    SqlMapping.get("adam_caomei_badge_apply_record.add",
                            applyRecordId,
                            uid,
                            badgeId,
                            performanceId,
                            proofImageUrl)
            );
        }
        log.info("[claimBadge] MQ耗时:{}ms, uid: {}, badgeId: {}", System.currentTimeMillis() - t, uid, badge.getBadgeId());

        return ResponseDto.success(applyRecordId);
    }

    /**
     * 同场次 type=2 徽章 ID（用于补签记录 performance_id 为空等历史数据的 OR 查询）。
     */
    private Set<String> type2BadgeIdsForPerformance(String performanceId) {
        if (StringUtils.isBlank(performanceId)) {
            return Collections.emptySet();
        }
        List<AdamCaomeiBadge> list = adamCaomeiBadgeMapper.selectList(
                Wrappers.lambdaQuery(AdamCaomeiBadge.class)
                        .eq(AdamCaomeiBadge::getType, 2)
                        .eq(AdamCaomeiBadge::getPerformanceId, performanceId));
        if (list == null || list.isEmpty()) {
            return Collections.emptySet();
        }
        return list.stream()
                .map(AdamCaomeiBadge::getBadgeId)
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.toSet());
    }

    /**
     * 用户对某场次是否存在指定审核状态的补签记录（按 performance_id 或同场徽章 ID 命中）。
     */
    private int countApplyForUserPerformanceAudit(String uid, String performanceId, int auditStatus) {
        if (StringUtils.isBlank(performanceId)) {
            return 0;
        }
        Set<String> samePerfBadgeIds = type2BadgeIdsForPerformance(performanceId);
        LambdaQueryWrapper<AdamCaomeiBadgeApplyRecord> qw =
                Wrappers.lambdaQuery(AdamCaomeiBadgeApplyRecord.class)
                        .eq(AdamCaomeiBadgeApplyRecord::getUserId, uid)
                        .eq(AdamCaomeiBadgeApplyRecord::getAuditStatus, auditStatus);
        if (samePerfBadgeIds.isEmpty()) {
            qw.eq(AdamCaomeiBadgeApplyRecord::getPerformanceId, performanceId);
        } else {
            qw.and(w -> w.eq(AdamCaomeiBadgeApplyRecord::getPerformanceId, performanceId)
                    .or()
                    .in(AdamCaomeiBadgeApplyRecord::getBadgeId, samePerfBadgeIds));
        }
        Integer n = badgeApplyRecordMapper.selectCount(qw);
        return n == null ? 0 : n;
    }

    private boolean isPerformanceOngoing(String performanceId) {
        if (StringUtils.isBlank(performanceId)) {
            return false;
        }
        AdamRdmService.PerformanceTimeRange r = adamRdmService.getPerformanceTimeRangeById(performanceId);
        if (r == null || r.getStart() == null || r.getEnd() == null) {
            return false;
        }
        java.time.LocalDateTime now = java.time.LocalDateTime.now();
        return (now.isEqual(r.getStart()) || now.isAfter(r.getStart()))
                && (now.isEqual(r.getEnd()) || now.isBefore(r.getEnd()));
    }
}
