"""
推荐接口实现
实现探索/利用分支、向量检索、Softmax抽样等功能
"""
import logging
import math
import random
import numpy as np
from typing import List, Dict, Optional

from app.service.recommendation_system.config import RECOMMENDATION_CONFIG, REDIS_KEY_USER_PREF_PREFIX
from app.service.recommendation_system.milvus_client import search_similar_vectors, query_random_candidates
from app.service.recommendation_system.precompute import compute_user_preference_vector
from app.service.recommendation_system.vector_utils import normalize_vector
from app.service.utils.redis_utils import Redis
import json

logger = logging.getLogger(__name__)


def get_user_preference_vector(user_id: int, category: str) -> Optional[np.ndarray]:
    """
    获取用户偏好向量
    
    Args:
        user_id: 用户ID
        category: 类别
    
    Returns:
        用户偏好向量（2048维），失败返回 None
    """
    # 1. 从 Redis 获取
    key = f"{REDIS_KEY_USER_PREF_PREFIX}:{user_id}:{category}"
    vector_json = Redis.read(key)
    
    if vector_json:
        try:
            vector_list = json.loads(vector_json)
            return np.array(vector_list, dtype=np.float32)
        except Exception as e:
            logger.warning(f"解析 Redis 向量失败 [user={user_id}, category={category}]: {e}")
    
    # 2. 如果不存在，实时计算
    logger.info(f"Redis 中不存在用户偏好向量，实时计算 [user={user_id}, category={category}]")
    preference_vector = compute_user_preference_vector(user_id, category)
    
    if preference_vector is not None:
        # 写入 Redis
        vector_json = json.dumps(preference_vector.tolist())
        Redis.write(
            key=key,
            value=vector_json,
            expire=RECOMMENDATION_CONFIG["redis_expire_seconds"]
        )
    
    return preference_vector


def explore_branch(category: str, style: Optional[str] = None) -> List[str]:
    """
    探索分支（随机推荐）
    
    Args:
        category: 类别
        style: 风格（可选）
    
    Returns:
        推荐结果列表，每个元素包含 path, style, category 等字段
    """
    # 查询候选（随机池）
    pool_size = 10  # 固定查询10个，然后随机选择
    
    candidates = query_random_candidates(category, style, limit=pool_size)
    
    if not candidates:
        logger.warning(f"探索分支：类别 {category} 没有候选数据")
        return []
    
    # 随机选择
    if len(candidates) > 1:
        import random
        candidates = random.sample(candidates, 1)
    
    # 格式化返回结果
    return [candidate.get("path", "") for candidate in candidates[:1]]


def exploit_branch(
    user_id: int,
    category: str,
    style: Optional[str] = None
) -> List[str]:
    """
    利用分支（基于向量相似度推荐）
    
    Args:
        user_id: 用户ID
        category: 类别
        num_recommendations: 返回数量
        style: 风格（可选，用于加分）
    
    Returns:
        推荐结果列表，每个元素包含 path, style, category, similarity, sample_score 等字段
    """
    # 1. 获取用户偏好向量
    embedding = get_user_preference_vector(user_id, category)
    
    if embedding is None:
        logger.warning(f"利用分支：无法获取用户偏好向量，回退到探索分支 [user={user_id}, category={category}]")
        return explore_branch(category, style)
    
    # 2. Milvus 相似度检索（内积 IP）
    topk = RECOMMENDATION_CONFIG["topk"]
    results = search_similar_vectors(embedding, category, topk)
    
    if not results:
        logger.warning(f"利用分支：向量检索无结果，回退到探索分支 [user={user_id}, category={category}]")
        return explore_branch(category, style)
    
    # 3. Style 加分（可选，需传入 style 参数）
    style_bonus = RECOMMENDATION_CONFIG["style_bonus"]
    if style:
        for result in results:
            similarity = result["score"]
            if result.get("style") == style:
                # 加分：相似度 * (1 + style_bonus)
                similarity = similarity * (1 + style_bonus)
            result["final_score"] = similarity
    else:
        for result in results:
            result["final_score"] = result["score"]
    
    # 4. Softmax 抽样
    scores = [r["final_score"] for r in results]
    probabilities = softmax_with_temperature(scores, RECOMMENDATION_CONFIG["softmax_temperature"])
    
    # 根据概率抽样
    if not results:
        return []

    selected_index = np.random.choice(len(results), size=1, p=probabilities, replace=False)
    selected_results = [results[int(selected_index[0])]]

    # 5. 返回结果
    return [result.get("path", "") for result in selected_results]


def softmax_with_temperature(scores: List[float], temperature: float = 1.0) -> List[float]:
    """
    Softmax 函数（带温度参数）
    
    Args:
        scores: 分数列表
        temperature: 温度参数
    
    Returns:
        概率列表
    """
    if not scores:
        return []
    
    # 除以温度
    scaled_scores = [s / temperature for s in scores]
    
    # 减去最大值（数值稳定性）
    max_score = max(scaled_scores)
    exp_scores = [math.exp(s - max_score) for s in scaled_scores]
    
    # 归一化
    sum_exp = sum(exp_scores)
    if sum_exp == 0:
        # 如果所有分数都是负无穷或非常小，返回均匀分布
        return [1.0 / len(scores)] * len(scores)
    
    probabilities = [exp_s / sum_exp for exp_s in exp_scores]
    return probabilities


def get_recommendations(
    user_id: int,
    category: str,
    style: Optional[str] = None
) -> List[str]:
    """
    获取推荐结果（主函数）
    
    Args:
        user_id: 用户ID
        category: 类别（如 female_skirt）
        num_recommendations: 返回推荐数量（默认 1）
        style: 风格（可选）：若传入，则在利用分支对同 style 的候选进行加分
    
    Returns:
        推荐结果列表，每个元素包含 path 等字段
    """
    try:
        # 1. 读取配置参数
        explore_ratio = RECOMMENDATION_CONFIG["explore_ratio"]
        
        # 2. 探索/利用决策
        r = random.random()  # 生成随机数 (0-1)
        
        if r < explore_ratio:
            logger.debug(f"探索分支 [user={user_id}, category={category}]")
            return explore_branch(category, style)

        logger.debug(f"利用分支 [user={user_id}, category={category}]")
        return exploit_branch(user_id, category, style)
        
    except Exception as e:
        logger.error(f"获取推荐结果失败 [user={user_id}, category={category}]: {e}", exc_info=True)
        # 容错：回退到探索分支
        return explore_branch(category, style)

