package com.zhentao.service.impl;

import com.zhentao.config.RecommendProps;
import com.zhentao.mapper.RecommendMapper;
import com.zhentao.mapper.UsersMapper;
import com.zhentao.pojo.Users;
import com.zhentao.service.RecommendService;
import com.zhentao.vo.RecommendUserVO;
import com.zhentao.dto.UserSearchQuery;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import javax.annotation.Resource;
import java.util.List;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.StringRedisTemplate;

@Service
public class RecommendServiceImpl implements RecommendService {

    private static final Logger log = LoggerFactory.getLogger(RecommendServiceImpl.class);

    @Resource
    private RecommendMapper recommendMapper;

    @Resource
    private UsersMapper usersMapper;

    @Resource
    private RecommendProps recommendProps;

    private final ObjectMapper objectMapper = new ObjectMapper();
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public List<RecommendUserVO> getRecommendedUsers(Integer userId, Integer oppoOnly, Integer limit) {
        if (userId == null) {
            throw new IllegalArgumentException("userId cannot be null");
        }
        // 强制只推荐异性，不允许推荐同性
        oppoOnly = 1; // 始终强制设置为1，只推荐异性
        if (limit == null || limit <= 0) {
            limit = 50;
        }
        // 先查询当前用户的性别信息，用于清除缓存和调试
        Users currentUser = null;
        try {
            currentUser = usersMapper.selectById(userId);
            if (currentUser != null) {
                log.info("当前用户ID: {}, 性别: {}", userId, currentUser.getGender());
            } else {
                log.warn("当前用户不存在，userId: {}", userId);
            }
        } catch (Exception ex) {
            log.warn("获取当前用户信息失败", ex);
        }
        
        // 清除可能存在的旧缓存（当用户性别改变时，缓存可能包含错误的数据）
        // 读取或生成候选池缓存
        int poolSize = Math.min(Math.max(limit * 5, 100), 500);
        String poolKey = "rec:pool:" + userId + ":" + oppoOnly + ":" + poolSize;
        List<RecommendUserVO> pool = null;
        
        // 如果启用了缓存，先尝试清除可能存在的旧缓存（基于用户ID的所有缓存）
        if (recommendProps.isCacheEnabled()) {
            try {
                // 清除该用户的所有推荐池缓存，确保获取最新数据
                String cachePattern = "rec:pool:" + userId + ":*";
                java.util.Set<String> keys = stringRedisTemplate.keys(cachePattern);
                if (keys != null && !keys.isEmpty()) {
                    stringRedisTemplate.delete(keys);
                    log.info("已清除用户 {} 的推荐缓存，共 {} 个键", userId, keys.size());
                }
            } catch (Exception ignore) {
                log.warn("清除缓存失败", ignore);
            }
        }
        // 始终从数据库查询最新数据，不依赖缓存（确保性别变更后能立即生效）
        try {
            pool = recommendMapper.selectRecommendedUsers(userId, oppoOnly, poolSize);
            log.info("推荐查询返回数据量: {}, oppoOnly: {}, userId: {}", pool != null ? pool.size() : 0, oppoOnly, userId);
            if (pool == null || pool.isEmpty()) {
                log.warn("推荐查询返回空结果，可能原因：1.当前用户性别未设置 2.数据库中没有异性用户 3.SQL查询条件过严");
                // 如果查询返回空，尝试直接查询数据库中的异性用户数量
                try {
                    QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
                    queryWrapper.eq("status", 1)
                            .isNotNull("gender")
                            .ne("gender", 0)
                            .ne("user_id", userId);
                    if (currentUser != null && currentUser.getGender() != null && currentUser.getGender() != 0) {
                        queryWrapper.ne("gender", currentUser.getGender());
                    }
                    long oppositeGenderCount = usersMapper.selectList(queryWrapper).size();
                    log.info("数据库中符合条件的异性用户数量: {}", oppositeGenderCount);
                    if (oppositeGenderCount > 0) {
                        log.warn("数据库中有 {} 个异性用户，但SQL查询返回空，可能是SQL查询逻辑有问题", oppositeGenderCount);
                    }
                } catch (Exception e) {
                    log.error("查询异性用户数量失败", e);
                }
            } else {
                log.info("返回数据中的性别分布: 男性={}, 女性={}, 未知={}", 
                    pool.stream().filter(u -> u.getGender() != null && u.getGender() == 1).count(),
                    pool.stream().filter(u -> u.getGender() != null && u.getGender() == 2).count(),
                    pool.stream().filter(u -> u.getGender() == null || u.getGender() == 0).count());
            }
        } catch (Exception ex) {
            log.error("selectRecommendedUsers failed, fallback to empty list (possibly DB not available)", ex);
            ex.printStackTrace();
            return java.util.Collections.emptyList();
        }
        
        // 查询成功后，更新缓存（可选，用于性能优化）
        if (recommendProps.isCacheEnabled() && pool != null && !pool.isEmpty()) {
            try {
                String json = objectMapper.writeValueAsString(pool);
                stringRedisTemplate.opsForValue().set(poolKey, json, recommendProps.getCacheTtlSeconds(), TimeUnit.SECONDS);
            } catch (Exception ignore) {}
        }

        // 过滤用户的"不喜欢"集合，避免再次出现
        try {
            java.util.Set<String> disliked = stringRedisTemplate.opsForSet().members("rec:dislike:user:" + userId);
            if (disliked != null && !disliked.isEmpty()) {
                pool.removeIf(u -> u.getUserId() != null && disliked.contains(String.valueOf(u.getUserId())));
            }
        } catch (Exception ignore) {}
        
        // 再次过滤：确保不包含当前用户本身和未完善性别信息的用户，以及同性用户
        if (pool == null) {
            pool = new ArrayList<>();
        }
        final Integer finalCurrentUserGender = currentUser != null ? currentUser.getGender() : null;
        pool.removeIf(u -> {
            if (u.getUserId() == null || u.getUserId().equals(userId)) {
                log.debug("过滤当前用户自己: userId={}", u.getUserId());
                return true;
            }
            if (u.getGender() == null || u.getGender() == 0) {
                log.debug("过滤未设置性别的用户: userId={}", u.getUserId());
                return true;
            }
            // 如果当前用户性别已设置，排除同性
            if (finalCurrentUserGender != null && finalCurrentUserGender != 0) {
                if (u.getGender().equals(finalCurrentUserGender)) {
                    log.debug("过滤同性用户: userId={}, gender={}, 当前用户gender={}", u.getUserId(), u.getGender(), finalCurrentUserGender);
                    return true;
                }
            }
            return false;
        });
        log.info("过滤后的推荐列表数量: {}", pool.size());
        
        log.info("过滤后的推荐列表数量: {}", pool != null ? pool.size() : 0);

        // 迭代式 MMR：每次从候选中选择 reRank 分最高者加入结果集
        double lambda = recommendProps.getMmrLambda();
        List<RecommendUserVO> result = new ArrayList<>();
        Set<String> seenBuckets = new HashSet<>();

        // 先按原始分降序，作为MMR的初始候选顺序（空值按0处理）
        pool.sort(Comparator.<RecommendUserVO>comparingDouble((RecommendUserVO u) -> safeScore(u.getCompatibilityScore())).reversed());

        while (result.size() < limit && !pool.isEmpty()) {
            double bestScore = Double.NEGATIVE_INFINITY;
            int bestIdx = -1;

            for (int i = 0; i < Math.min(pool.size(), 200); i++) { // 每轮评估前200个
                RecommendUserVO cand = pool.get(i);

                // 桶去重：过多重复的城/校/职，不再考虑
                String bucket = (cand.getCityId() == null ? "-" : cand.getCityId()) + "|"
                        + (cand.getSchoolName() == null ? "-" : cand.getSchoolName()) + "|"
                        + (cand.getJobTitle() == null ? "-" : cand.getJobTitle());
                if (seenBuckets.contains(bucket)) {
                    continue;
                }

                double maxSim = 0.0;
                for (RecommendUserVO chosen : result) {
                    double sim = 0.0;
                    // 余弦相似度扩展：兴趣 + 职位 + 院校 三路向量加权
                    double hobbyCos = cosineSimilarity(tokenizeHobby(cand.getHobby()), tokenizeHobby(chosen.getHobby()));
                    double jobCos = cosineSimilarity(tokenizeText(cand.getJobTitle()), tokenizeText(chosen.getJobTitle()));
                    double schoolCos = cosineSimilarity(tokenizeText(cand.getSchoolName()), tokenizeText(chosen.getSchoolName()));
                    double cosCombined = 0.5 * hobbyCos + 0.3 * jobCos + 0.2 * schoolCos; // 权重可后续配置化
                    sim += cosCombined;
                    if (sim > maxSim) maxSim = sim;
                }

                double reRankScore = safeScore(cand.getCompatibilityScore()) - lambda * maxSim * 100.0;
                if (reRankScore > bestScore) {
                    bestScore = reRankScore;
                    bestIdx = i;
                }
            }

            if (bestIdx >= 0) {
                RecommendUserVO chosen = pool.remove(bestIdx);
                String bucket = (chosen.getCityId() == null ? "-" : chosen.getCityId()) + "|"
                        + (chosen.getSchoolName() == null ? "-" : chosen.getSchoolName()) + "|"
                        + (chosen.getJobTitle() == null ? "-" : chosen.getJobTitle());
                seenBuckets.add(bucket);
                result.add(chosen);
            } else {
                break;
            }
        }

        // 写入最终 TopN 缓存
        if (recommendProps.isCacheEnabled() && !result.isEmpty()) {
            try {
                String topKey = "rec:top:" + userId + ":" + oppoOnly + ":" + limit;
                String json = objectMapper.writeValueAsString(result);
                stringRedisTemplate.opsForValue().set(topKey, json, recommendProps.getCacheTtlSeconds(), TimeUnit.SECONDS);
            } catch (Exception ignore) {}
        }

        return result;
    }

    @Override
    public List<RecommendUserVO> getRecommendedUsers(Integer userId, Integer oppoOnly, Integer limit, List<Integer> excludeIds) {
        // 调用原方法获取推荐列表
        List<RecommendUserVO> allUsers = getRecommendedUsers(userId, oppoOnly, limit * 10); // 获取更多数据以便过滤
        
        if (excludeIds == null || excludeIds.isEmpty()) {
            // 如果没有排除列表，直接返回前limit个
            return allUsers.stream().limit(limit).collect(java.util.stream.Collectors.toList());
        }
        
        // 过滤掉已显示的用户
        java.util.Set<Integer> excludeSet = new java.util.HashSet<>(excludeIds);
        List<RecommendUserVO> filtered = allUsers.stream()
            .filter(u -> u.getUserId() != null && !excludeSet.contains(u.getUserId()))
            .limit(limit)
            .collect(java.util.stream.Collectors.toList());
        
        log.info("排除已显示用户后，返回 {} 个用户（排除 {} 个用户ID）", filtered.size(), excludeIds.size());
        
        // 如果过滤后数量不足，说明所有用户都显示过了，返回空列表让前端重新开始
        if (filtered.size() < limit && allUsers.size() > 0) {
            log.info("所有用户都已显示过，建议前端重新开始");
        }
        
        return filtered;
    }

    @Override
    public List<RecommendUserVO> searchByRules(UserSearchQuery q) {
        if (q == null || q.getUserId() == null) {
            throw new IllegalArgumentException("userId cannot be null");
        }
        int limit = (q.getLimit() == null || q.getLimit() <= 0) ? 20 : q.getLimit();
        int offset = (q.getOffset() == null || q.getOffset() < 0) ? 0 : q.getOffset();

        // 计算出生日期范围（年龄 -> 出生日期）
        String birthMin = null, birthMax = null; // YYYY-MM-DD
        java.time.LocalDate today = java.time.LocalDate.now();
        if (q.getAgeMax() != null) {
            birthMin = today.minusYears(q.getAgeMax()).toString();
        }
        if (q.getAgeMin() != null) {
            birthMax = today.minusYears(q.getAgeMin()).toString();
        }

        // 兴趣标签转 JSON 数组字符串
        String hobbyJson = null;
        if (q.getHobbyTags() != null && !q.getHobbyTags().isEmpty()) {
            try { hobbyJson = objectMapper.writeValueAsString(q.getHobbyTags()); } catch (Exception ignore) {}
        }

        // 读取不喜欢集合，作为排除列表
        java.util.List<Integer> excludeIds = new java.util.ArrayList<>();
        try {
            java.util.Set<String> disliked = stringRedisTemplate.opsForSet().members("rec:dislike:user:" + q.getUserId());
            if (disliked != null) {
                for (String s : disliked) { try { excludeIds.add(Integer.parseInt(s)); } catch (Exception ignore) {} }
            }
        } catch (Exception ignore) {}

        // 获取当前用户的性别信息
        Integer currentUserGender = null;
        try {
            Users currentUser = usersMapper.selectById(q.getUserId());
            if (currentUser != null) {
                currentUserGender = currentUser.getGender();
            }
        } catch (Exception ex) {
            log.warn("Failed to get current user gender, userId: " + q.getUserId(), ex);
        }
        
        // 如果当前用户未完善性别信息，返回空列表
        if (currentUserGender == null || currentUserGender == 0) {
            log.warn("Current user has no gender information, userId: " + q.getUserId());
            return java.util.Collections.emptyList();
        }
        
        // 召回
        List<RecommendUserVO> pool;
        try {
            pool = recommendMapper.selectByRules(q, birthMin, birthMax, hobbyJson, excludeIds, Math.min(limit * 5, 200), offset);
        } catch (Exception ex) {
            log.error("selectByRules failed, fallback to empty list (possibly DB not available)", ex);
            return java.util.Collections.emptyList();
        }
        
        // 过滤：确保不包含当前用户本身和未完善性别信息的用户，只推荐异性
        if (pool != null && !pool.isEmpty()) {
            final Integer finalCurrentUserGender = currentUserGender;
            pool.removeIf(user -> 
                user.getUserId() == null || 
                user.getUserId().equals(q.getUserId()) || 
                user.getGender() == null || 
                user.getGender() == 0 ||
                user.getGender().equals(finalCurrentUserGender)  // 排除同性
            );
        }

        // 重排（沿用 MMR）
        double lambda = recommendProps.getMmrLambda();
        java.util.List<RecommendUserVO> result = new java.util.ArrayList<>();
        java.util.Set<String> seenBuckets = new java.util.HashSet<>();
        pool.sort(java.util.Comparator.<RecommendUserVO>comparingDouble((RecommendUserVO u) -> safeScore(u.getCompatibilityScore())).reversed());
        while (result.size() < limit && !pool.isEmpty()) {
            double bestScore = Double.NEGATIVE_INFINITY; int bestIdx = -1;
            for (int i = 0; i < Math.min(pool.size(), 200); i++) {
                RecommendUserVO cand = pool.get(i);
                String bucket = (cand.getCityId() == null ? "-" : cand.getCityId()) + "|"
                        + (cand.getSchoolName() == null ? "-" : cand.getSchoolName()) + "|"
                        + (cand.getJobTitle() == null ? "-" : cand.getJobTitle());
                if (seenBuckets.contains(bucket)) continue;
                double hobbyCos = cosineSimilarity(tokenizeHobby(cand.getHobby()), new String[0]);
                double jobCos = cosineSimilarity(tokenizeText(cand.getJobTitle()), new String[0]);
                double schoolCos = cosineSimilarity(tokenizeText(cand.getSchoolName()), new String[0]);
                double maxSim = 0.5 * hobbyCos + 0.3 * jobCos + 0.2 * schoolCos;
                double reRankScore = safeScore(cand.getCompatibilityScore()) - lambda * maxSim * 100.0;
                if (reRankScore > bestScore) { bestScore = reRankScore; bestIdx = i; }
            }
            if (bestIdx >= 0) {
                RecommendUserVO chosen = pool.remove(bestIdx);
                String bucket = (chosen.getCityId() == null ? "-" : chosen.getCityId()) + "|"
                        + (chosen.getSchoolName() == null ? "-" : chosen.getSchoolName()) + "|"
                        + (chosen.getJobTitle() == null ? "-" : chosen.getJobTitle());
                seenBuckets.add(bucket); result.add(chosen);
            } else break;
        }
        return result;
    }

    @Override
    public List<com.zhentao.pojo.Province> getAllProvinces() {
        return recommendMapper.selectAllProvinces();
    }

    @Override
    public List<com.zhentao.pojo.City> getAllCities() {
        return recommendMapper.selectAllCities();
    }

    @Override
    public List<com.zhentao.pojo.City> getCitiesByProvince(Integer provinceId) {
        if (provinceId == null) return java.util.Collections.emptyList();
        return recommendMapper.selectCitiesByProvince(provinceId);
    }

    @Override
    public List<com.zhentao.pojo.Area> getAreasByCity(Integer cityId) {
        if (cityId == null) return java.util.Collections.emptyList();
        return recommendMapper.selectAreasByCity(cityId);
    }

    private boolean safeEq(String a, String b) {
        if (a == null || b == null) return false;
        return a.equals(b);
    }

    private boolean jobSimilar(String a, String b) {
        if (a == null || b == null) return false;
        String as = a.toLowerCase();
        String bs = b.toLowerCase();
        return as.equals(bs) || as.contains(bs) || bs.contains(as);
    }

    private boolean hobbyOverlap(String a, String b) {
        if (a == null || b == null) return false;
        String as = a.toLowerCase();
        String bs = b.toLowerCase();
        String[] tokens = as.replace('[',' ').replace(']',' ').replace('"',' ').split("[, ]+");
        for (String t : tokens) {
            if (t == null || t.trim().isEmpty()) continue;
            if (bs.contains(t.trim())) return true;
        }
        return false;
    }

    private String[] tokenizeHobby(String hobbyJson) {
        if (hobbyJson == null) return new String[0];
        String merged = hobbyJson.toLowerCase().replace('[',' ').replace(']',' ').replace('"',' ');
        return merged.split("[^a-z0-9\\u4e00-\\u9fa5]+");
    }

    private String[] tokenizeText(String text) {
        if (text == null) return new String[0];
        return text.toLowerCase().split("[^a-z0-9\\u4e00-\\u9fa5]+");
    }

    private double cosineSimilarity(String[] a, String[] b) {
        if (a == null || b == null || a.length == 0 || b.length == 0) return 0.0;
        java.util.Map<String, int[]> map = new java.util.HashMap<>();
        for (String t : a) {
            if (t == null || t.isEmpty()) continue;
            map.computeIfAbsent(t, k -> new int[2])[0]++;
        }
        for (String t : b) {
            if (t == null || t.isEmpty()) continue;
            map.computeIfAbsent(t, k -> new int[2])[1]++;
        }
        double dot = 0, na = 0, nb = 0;
        for (int[] v : map.values()) {
            dot += v[0] * v[1];
            na += v[0] * v[0];
            nb += v[1] * v[1];
        }
        if (na == 0 || nb == 0) return 0.0;
        return dot / (Math.sqrt(na) * Math.sqrt(nb));
    }

    private double safeScore(Number n) {
        if (n == null) return 0.0;
        try { return n.doubleValue(); } catch (Exception ignore) { return 0.0; }
    }
}


