package com.example.service;

import com.example.common.config.TokenUtils;
import com.example.common.enums.ResultCodeEnum;
import com.example.entity.Account;
import com.example.entity.ResourceRating;
import com.example.entity.TreatmentResource;
import com.example.exception.CustomException;
import com.example.mapper.ResourceCategoryMapper;
import com.example.mapper.ResourceRatingMapper;
import com.example.mapper.TreatmentResourceMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

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

/**
 * 基于协同过滤的推荐服务
 */
@Service
public class RecommendationService {

    @Resource
    private ResourceRatingMapper resourceRatingMapper;
    
    @Resource
    private TreatmentResourceMapper treatmentResourceMapper;
    
    @Resource
    private ResourceCategoryMapper resourceCategoryMapper;
    
    /**
     * 获取针对特定用户的资源推荐
     * @param userId 用户ID
     * @param limit 推荐数量限制
     * @return 推荐资源列表
     */
    public List<Map<String, Object>> getRecommendationsForUser(Integer userId, Integer limit) {
        // 获取当前登录用户
        Account currentUser = TokenUtils.getCurrentUser();
        if (currentUser == null || !userId.equals(currentUser.getId())) {
            throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR);
        }
        
        // 只有患者才能获取推荐
        if (!"patient".equals(currentUser.getRole())) {
            throw new CustomException(ResultCodeEnum.PARAM_ERROR.code, "只有患者才能获取推荐");
        }
        
        // 获取所有评分数据
        List<ResourceRating> allRatings = resourceRatingMapper.selectList(null);
        
        // 获取目标用户的评分记录
        List<ResourceRating> userRatings = allRatings.stream()
            .filter(r -> r.getPatientId().equals(userId))
            .collect(Collectors.toList());
        
        // 如果用户没有评分记录，返回热门资源
        if (userRatings.isEmpty()) {
            return getPopularResources(limit);
        }
        
        // 计算用户相似度
        List<Map<String, Object>> userSimilarities = calculateUserSimilarity(userId, allRatings);
        
        // 获取用户已评分的资源ID
        Set<Integer> userRatedResourceIds = userRatings.stream()
            .map(ResourceRating::getResourceId)
            .collect(Collectors.toSet());
        
        // 获取所有已审核通过的资源
        List<TreatmentResource> allResources = treatmentResourceMapper.selectAllAudited("");
        
        // 获取用户未评分的资源
        List<TreatmentResource> unratedResources = allResources.stream()
            .filter(resource -> !userRatedResourceIds.contains(resource.getId()))
            .collect(Collectors.toList());
        
        // 对每个未评分资源预测评分并生成推荐
        List<Map<String, Object>> recommendationsWithScores = new ArrayList<>();
        
        for (TreatmentResource resource : unratedResources) {
            double predictedRating = predictRating(resource.getId(), userSimilarities, allRatings);
            
            // 只推荐预测评分大于0的资源
            if (predictedRating > 0) {
                Map<String, Object> recommendation = new HashMap<>();
                recommendation.put("resource", resource);
                recommendation.put("predictedRating", predictedRating);
                recommendation.put("similarityScore", Math.min(100, predictedRating * 20)); // 将5分制转换为100分制
                recommendation.put("reason", getRecommendationReason(resource, userSimilarities, allRatings, userRatings, allResources));
                
                // 添加分类名称
                com.example.entity.ResourceCategory category = resourceCategoryMapper.selectById(resource.getCategoryId());
                if (category != null) {
                    recommendation.put("categoryName", category.getName());
                }
                
                recommendationsWithScores.add(recommendation);
            }
        }
        
        // 按预测评分降序排序，取前几个作为推荐
        recommendationsWithScores.sort((a, b) -> 
            Double.compare((Double) b.get("predictedRating"), (Double) a.get("predictedRating")));
        
        // 限制推荐数量
        int resultLimit = Math.min(limit, recommendationsWithScores.size());
        return recommendationsWithScores.subList(0, resultLimit);
    }
    
    /**
     * 获取热门资源作为推荐（用于冷启动问题）
     */
    private List<Map<String, Object>> getPopularResources(Integer limit) {
        // 获取所有已审核通过的资源
        List<TreatmentResource> allResources = treatmentResourceMapper.selectAllAudited("");
        
        // 按照访问次数和下载次数之和排序
        allResources.sort((a, b) -> {
            int aPopularity = (a.getVisitCount() != null ? a.getVisitCount() : 0) + 
                             (a.getDownloadCount() != null ? a.getDownloadCount() : 0);
            int bPopularity = (b.getVisitCount() != null ? b.getVisitCount() : 0) + 
                             (b.getDownloadCount() != null ? b.getDownloadCount() : 0);
            return Integer.compare(bPopularity, aPopularity);
        });
        
        // 转换为推荐格式
        List<Map<String, Object>> recommendations = new ArrayList<>();
        int resultLimit = Math.min(limit, allResources.size());
        
        for (int i = 0; i < resultLimit; i++) {
            TreatmentResource resource = allResources.get(i);
            Map<String, Object> recommendation = new HashMap<>();
            recommendation.put("resource", resource);
            recommendation.put("predictedRating", 4.0); // 默认预测评分
            recommendation.put("similarityScore", 80.0); // 默认相似度分数
            recommendation.put("reason", "系统热门资源推荐");
            
            // 添加分类名称
            com.example.entity.ResourceCategory category = resourceCategoryMapper.selectById(resource.getCategoryId());
            if (category != null) {
                recommendation.put("categoryName", category.getName());
            }
            
            recommendations.add(recommendation);
        }
        
        return recommendations;
    }
    
    /**
     * 计算用户相似度 - 使用余弦相似度
     */
    private List<Map<String, Object>> calculateUserSimilarity(Integer targetUserId, List<ResourceRating> allRatings) {
        // 获取目标用户的评分记录
        List<ResourceRating> targetUserRatings = allRatings.stream()
            .filter(r -> r.getPatientId().equals(targetUserId))
            .collect(Collectors.toList());
        
        if (targetUserRatings.isEmpty()) {
            return Collections.emptyList();
        }
        
        // 用户ID列表（去除目标用户）
        List<Integer> userIds = allRatings.stream()
            .map(ResourceRating::getPatientId)
            .distinct()
            .filter(id -> !id.equals(targetUserId))
            .collect(Collectors.toList());
        
        // 计算每个用户与目标用户的相似度
        List<Map<String, Object>> similarities = new ArrayList<>();
        
        for (Integer userId : userIds) {
            // 获取当前用户的评分记录
            List<ResourceRating> userRatings = allRatings.stream()
                .filter(r -> r.getPatientId().equals(userId))
                .collect(Collectors.toList());
            
            if (userRatings.isEmpty()) {
                continue;
            }
            
            // 找出两个用户共同评分的资源
            Set<Integer> targetResourceIds = targetUserRatings.stream()
                .map(ResourceRating::getResourceId)
                .collect(Collectors.toSet());
            
            Set<Integer> userResourceIds = userRatings.stream()
                .map(ResourceRating::getResourceId)
                .collect(Collectors.toSet());
            
            Set<Integer> commonResourceIds = new HashSet<>(targetResourceIds);
            commonResourceIds.retainAll(userResourceIds);
            
            // 如果没有共同评分的资源，相似度为0
            if (commonResourceIds.isEmpty()) {
                continue;
            }
            
            // 提取两个用户对共同资源的评分向量
            double[] targetVector = new double[commonResourceIds.size()];
            double[] userVector = new double[commonResourceIds.size()];
            
            int index = 0;
            for (Integer resourceId : commonResourceIds) {
                double targetRating = targetUserRatings.stream()
                    .filter(r -> r.getResourceId().equals(resourceId))
                    .findFirst()
                    .map(r -> (double) r.getRating())
                    .orElse(0.0);
                
                double userRating = userRatings.stream()
                    .filter(r -> r.getResourceId().equals(resourceId))
                    .findFirst()
                    .map(r -> (double) r.getRating())
                    .orElse(0.0);
                
                targetVector[index] = targetRating;
                userVector[index] = userRating;
                index++;
            }
            
            // 计算余弦相似度
            // 余弦相似度 = (A·B) / (||A|| * ||B||)
            double dotProduct = 0.0;
            double targetMagnitude = 0.0;
            double userMagnitude = 0.0;
            
            for (int i = 0; i < targetVector.length; i++) {
                dotProduct += targetVector[i] * userVector[i];
                targetMagnitude += targetVector[i] * targetVector[i];
                userMagnitude += userVector[i] * userVector[i];
            }
            
            targetMagnitude = Math.sqrt(targetMagnitude);
            userMagnitude = Math.sqrt(userMagnitude);
            
            // 避免除以零
            double similarity = (targetMagnitude > 0 && userMagnitude > 0) 
                ? dotProduct / (targetMagnitude * userMagnitude)
                : 0;
            
            Map<String, Object> userSimilarity = new HashMap<>();
            userSimilarity.put("userId", userId);
            userSimilarity.put("similarity", similarity);
            similarities.add(userSimilarity);
        }
        
        // 按相似度降序排序
        similarities.sort((a, b) -> Double.compare(
            (Double) b.get("similarity"), 
            (Double) a.get("similarity")
        ));
        
        return similarities;
    }
    
    /**
     * 预测用户对资源的评分
     */
    private double predictRating(Integer resourceId, List<Map<String, Object>> userSimilarities, 
                                List<ResourceRating> allRatings) {
        // 获取已对该资源评分的相似用户
        List<Map<String, Object>> similarUsersWithRating = userSimilarities.stream()
            .filter(s -> allRatings.stream()
                .anyMatch(r -> r.getPatientId().equals(s.get("userId")) && 
                           r.getResourceId().equals(resourceId)))
            .collect(Collectors.toList());
        
        // 如果没有相似用户对该资源评分，返回默认预测值
        if (similarUsersWithRating.isEmpty()) {
            return 0;
        }
        
        // 计算加权评分和
        double weightedSum = 0.0;
        double similaritySum = 0.0;
        
        for (Map<String, Object> user : similarUsersWithRating) {
            Integer userId = (Integer) user.get("userId");
            double similarity = (Double) user.get("similarity");
            
            // 找到该用户对该资源的评分
            double rating = allRatings.stream()
                .filter(r -> r.getPatientId().equals(userId) && r.getResourceId().equals(resourceId))
                .findFirst()
                .map(r -> (double) r.getRating())
                .orElse(0.0);
            
            // 加权和
            weightedSum += similarity * rating;
            similaritySum += similarity;
        }
        
        // 计算加权平均
        return similaritySum > 0 ? weightedSum / similaritySum : 0;
    }
    
    /**
     * 获取推荐理由
     */
    private String getRecommendationReason(TreatmentResource resource, 
                                         List<Map<String, Object>> userSimilarities,
                                         List<ResourceRating> allRatings,
                                         List<ResourceRating> userRatings,
                                         List<TreatmentResource> allResources) {
        // 找出评分最高的相似用户对该资源的评价
        List<Map<String, Object>> topSimilarUsers = userSimilarities.stream()
            .limit(3)
            .collect(Collectors.toList());
        
        // 找出这些用户中有对该资源评分的用户
        List<Map<String, Object>> usersWithRating = topSimilarUsers.stream()
            .filter(s -> allRatings.stream()
                .anyMatch(r -> r.getPatientId().equals(s.get("userId")) && 
                           r.getResourceId().equals(resource.getId())))
            .collect(Collectors.toList());
        
        if (usersWithRating.isEmpty()) {
            // 基于内容的推荐理由
            if (!userRatings.isEmpty()) {
                // 查找用户评分最高的资源
                ResourceRating topRatedRating = userRatings.stream()
                    .max(Comparator.comparing(ResourceRating::getRating))
                    .orElse(null);
                
                if (topRatedRating != null) {
                    // 获取用户最喜欢的资源详情
                    Integer topRatedResourceId = topRatedRating.getResourceId();
                    TreatmentResource topRatedResource = allResources.stream()
                        .filter(r -> r.getId().equals(topRatedResourceId))
                        .findFirst()
                        .orElse(null);
                    
                    // 如果当前资源与用户最喜欢的资源属于同一分类
                    if (topRatedResource != null && resource.getCategoryId().equals(topRatedResource.getCategoryId())) {
                        return "根据您对同类资源的偏好推荐";
                    }
                    
                    // 如果当前资源适用的症状与用户评分高的资源相似
                    List<String> userPreferredSymptoms = userRatings.stream()
                        .filter(r -> r.getRating() >= 4)
                        .map(r -> {
                            // 获取资源详情
                            Optional<TreatmentResource> res = allResources.stream()
                                .filter(tr -> tr.getId().equals(r.getResourceId()))
                                .findFirst();
                            return res.map(TreatmentResource::getApplicableSymptoms).orElse("");
                        })
                        .flatMap(s -> Arrays.stream(s.split("[,、]")))
                        .map(String::trim)
                        .filter(s -> !s.isEmpty())
                        .collect(Collectors.toList());
                    
                    List<String> resourceSymptoms = Arrays.stream(
                            resource.getApplicableSymptoms() != null 
                            ? resource.getApplicableSymptoms().split("[,、]") 
                            : new String[0])
                        .map(String::trim)
                        .filter(s -> !s.isEmpty())
                        .collect(Collectors.toList());
                    
                    boolean hasCommonSymptoms = resourceSymptoms.stream()
                        .anyMatch(userPreferredSymptoms::contains);
                    
                    if (hasCommonSymptoms) {
                        return "包含您可能关注的症状";
                    }
                }
            }
            
            return "基于系统热门资源推荐";
        }
        
        // 基于相似用户的推荐理由
        if (usersWithRating.size() == 1) {
            return "与您兴趣相似的用户喜欢此资源";
        }
        
        return usersWithRating.size() + "位与您兴趣相似的用户喜欢此资源";
    }
} 