"""
等级服务

提供用户等级管理功能：
- 经验值获取与计算
- 等级升级判断
- 每日签到
- 排行榜查询
"""

from datetime import datetime, date, timedelta
from typing import Optional, List, Dict, Any, Tuple
from sqlalchemy import select, func, and_, desc
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.logger import get_logger
from app.models.level import (
    UserLevel, ExpHistory, LevelConfig, ExpType
)
from app.models.user import User

logger = get_logger(__name__)


# 经验值配置
EXP_CONFIG = {
    # 每日签到
    "login_base": 10,           # 基础签到经验
    "login_streak_7": 20,       # 连续7天额外奖励
    "login_streak_30": 50,      # 连续30天额外奖励
    
    # 考试
    "exam_pass": 50,            # 通过考试
    "exam_excellent": 30,       # 90分以上额外
    "exam_perfect": 50,         # 满分额外
    
    # 练习
    "practice_complete": 20,    # 完成练习
    "practice_good": 10,        # 80分以上额外
    
    # 陪练
    "training_complete": 30,    # 完成陪练
    "training_good": 15,        # 80分以上额外
    
    # 任务
    "task_complete": 40,        # 完成任务
}


class LevelService:
    """等级服务"""
    
    def __init__(self, db: AsyncSession):
        self.db = db
        self._level_configs: Optional[List[LevelConfig]] = None
    
    async def _get_level_configs(self) -> List[LevelConfig]:
        """获取等级配置（带缓存）"""
        if self._level_configs is None:
            result = await self.db.execute(
                select(LevelConfig).order_by(LevelConfig.level)
            )
            self._level_configs = list(result.scalars().all())
        return self._level_configs
    
    async def get_or_create_user_level(self, user_id: int) -> UserLevel:
        """
        获取或创建用户等级记录
        
        Args:
            user_id: 用户ID
            
        Returns:
            UserLevel 对象
        """
        result = await self.db.execute(
            select(UserLevel).where(UserLevel.user_id == user_id)
        )
        user_level = result.scalar_one_or_none()
        
        if not user_level:
            user_level = UserLevel(
                user_id=user_id,
                level=1,
                exp=0,
                total_exp=0,
                login_streak=0,
                max_login_streak=0
            )
            self.db.add(user_level)
            await self.db.flush()
            logger.info(f"为用户 {user_id} 创建等级记录")
        
        return user_level
    
    async def get_user_level_info(self, user_id: int) -> Dict[str, Any]:
        """
        获取用户等级详细信息
        
        Args:
            user_id: 用户ID
            
        Returns:
            包含等级、经验值、称号等信息的字典
        """
        user_level = await self.get_or_create_user_level(user_id)
        level_configs = await self._get_level_configs()
        
        # 获取当前等级配置
        current_config = next(
            (c for c in level_configs if c.level == user_level.level),
            level_configs[0] if level_configs else None
        )
        
        # 获取下一等级配置
        next_config = next(
            (c for c in level_configs if c.level == user_level.level + 1),
            None
        )
        
        # 计算升级所需经验
        exp_to_next_level = 0
        next_level_total_exp = 0
        if next_config:
            next_level_total_exp = next_config.total_exp_required
            exp_to_next_level = next_level_total_exp - user_level.total_exp
            if exp_to_next_level < 0:
                exp_to_next_level = 0
        
        return {
            "user_id": user_id,
            "level": user_level.level,
            "exp": user_level.exp,
            "total_exp": user_level.total_exp,
            "title": current_config.title if current_config else "初学者",
            "color": current_config.color if current_config else "#909399",
            "login_streak": user_level.login_streak,
            "max_login_streak": user_level.max_login_streak,
            "last_checkin_at": user_level.last_checkin_at.isoformat() if user_level.last_checkin_at else None,
            "next_level_exp": next_level_total_exp,
            "exp_to_next_level": exp_to_next_level,
            "is_max_level": next_config is None,
        }
    
    async def add_exp(
        self,
        user_id: int,
        exp_amount: int,
        exp_type: str,
        description: str,
        source_id: Optional[int] = None
    ) -> Tuple[UserLevel, bool, Optional[int]]:
        """
        增加用户经验值
        
        Args:
            user_id: 用户ID
            exp_amount: 经验值数量
            exp_type: 经验值类型
            description: 描述
            source_id: 来源ID（可选）
            
        Returns:
            (用户等级对象, 是否升级, 新等级)
        """
        if exp_amount <= 0:
            logger.warning(f"尝试增加非正数经验值: {exp_amount}")
            return await self.get_or_create_user_level(user_id), False, None
        
        user_level = await self.get_or_create_user_level(user_id)
        level_before = user_level.level
        
        # 增加经验值
        user_level.exp += exp_amount
        user_level.total_exp += exp_amount
        
        # 检查是否升级
        level_configs = await self._get_level_configs()
        leveled_up = False
        new_level = None
        
        for config in level_configs:
            if config.level > user_level.level and user_level.total_exp >= config.total_exp_required:
                user_level.level = config.level
                leveled_up = True
                new_level = config.level
        
        # 记录经验值历史
        exp_history = ExpHistory(
            user_id=user_id,
            exp_change=exp_amount,
            exp_type=exp_type,
            source_id=source_id,
            description=description,
            level_before=level_before,
            level_after=user_level.level
        )
        self.db.add(exp_history)
        
        await self.db.flush()
        
        if leveled_up:
            logger.info(f"用户 {user_id} 升级: {level_before} -> {new_level}")
        
        logger.info(f"用户 {user_id} 获得 {exp_amount} 经验值: {description}")
        
        return user_level, leveled_up, new_level
    
    async def daily_checkin(self, user_id: int) -> Dict[str, Any]:
        """
        每日签到
        
        Args:
            user_id: 用户ID
            
        Returns:
            签到结果
        """
        user_level = await self.get_or_create_user_level(user_id)
        today = date.today()
        
        # 检查今天是否已签到
        if user_level.last_login_date == today:
            return {
                "success": False,
                "message": "今天已经签到过了",
                "exp_gained": 0,
                "login_streak": user_level.login_streak,
                "already_checked_in": True
            }
        
        # 计算连续登录
        yesterday = today - timedelta(days=1)
        if user_level.last_login_date == yesterday:
            # 连续登录
            user_level.login_streak += 1
        else:
            # 中断了，重新计算
            user_level.login_streak = 1
        
        # 更新最长连续登录记录
        if user_level.login_streak > user_level.max_login_streak:
            user_level.max_login_streak = user_level.login_streak
        
        user_level.last_login_date = today
        user_level.last_checkin_at = datetime.now()
        
        # 计算签到经验
        exp_gained = EXP_CONFIG["login_base"]
        bonus_exp = 0
        bonus_reason = []
        
        # 连续登录奖励
        if user_level.login_streak >= 30 and user_level.login_streak % 30 == 0:
            bonus_exp += EXP_CONFIG["login_streak_30"]
            bonus_reason.append(f"连续{user_level.login_streak}天")
        elif user_level.login_streak >= 7 and user_level.login_streak % 7 == 0:
            bonus_exp += EXP_CONFIG["login_streak_7"]
            bonus_reason.append(f"连续{user_level.login_streak}天")
        
        total_exp = exp_gained + bonus_exp
        
        # 添加经验值
        description = f"每日签到"
        if bonus_reason:
            description += f"（{', '.join(bonus_reason)}奖励）"
        
        _, leveled_up, new_level = await self.add_exp(
            user_id=user_id,
            exp_amount=total_exp,
            exp_type=ExpType.LOGIN,
            description=description
        )
        
        await self.db.commit()
        
        return {
            "success": True,
            "message": "签到成功",
            "exp_gained": total_exp,
            "base_exp": exp_gained,
            "bonus_exp": bonus_exp,
            "login_streak": user_level.login_streak,
            "leveled_up": leveled_up,
            "new_level": new_level,
            "already_checked_in": False
        }
    
    async def get_exp_history(
        self,
        user_id: int,
        limit: int = 50,
        offset: int = 0,
        exp_type: Optional[str] = None
    ) -> Tuple[List[Dict[str, Any]], int]:
        """
        获取经验值历史
        
        Args:
            user_id: 用户ID
            limit: 限制数量
            offset: 偏移量
            exp_type: 类型筛选
            
        Returns:
            (历史记录列表, 总数)
        """
        # 构建查询
        query = select(ExpHistory).where(ExpHistory.user_id == user_id)
        
        if exp_type:
            query = query.where(ExpHistory.exp_type == exp_type)
        
        # 获取总数
        count_query = select(func.count(ExpHistory.id)).where(ExpHistory.user_id == user_id)
        if exp_type:
            count_query = count_query.where(ExpHistory.exp_type == exp_type)
        total_result = await self.db.execute(count_query)
        total = total_result.scalar() or 0
        
        # 获取记录
        query = query.order_by(desc(ExpHistory.created_at)).limit(limit).offset(offset)
        result = await self.db.execute(query)
        records = result.scalars().all()
        
        history = [
            {
                "id": r.id,
                "exp_change": r.exp_change,
                "exp_type": r.exp_type,
                "description": r.description,
                "level_before": r.level_before,
                "level_after": r.level_after,
                "created_at": r.created_at.isoformat() if r.created_at else None
            }
            for r in records
        ]
        
        return history, total
    
    async def get_leaderboard(
        self,
        limit: int = 50,
        offset: int = 0
    ) -> Tuple[List[Dict[str, Any]], int]:
        """
        获取等级排行榜
        
        Args:
            limit: 限制数量
            offset: 偏移量
            
        Returns:
            (排行榜列表, 总数)
        """
        # 获取总数
        count_query = select(func.count(UserLevel.id))
        total_result = await self.db.execute(count_query)
        total = total_result.scalar() or 0
        
        # 获取排行榜（按等级和总经验值排序）
        query = (
            select(UserLevel, User)
            .join(User, UserLevel.user_id == User.id)
            .where(User.is_deleted == False)
            .order_by(desc(UserLevel.level), desc(UserLevel.total_exp))
            .limit(limit)
            .offset(offset)
        )
        result = await self.db.execute(query)
        rows = result.all()
        
        # 获取等级配置
        level_configs = await self._get_level_configs()
        config_map = {c.level: c for c in level_configs}
        
        leaderboard = []
        for i, (user_level, user) in enumerate(rows):
            config = config_map.get(user_level.level)
            leaderboard.append({
                "rank": offset + i + 1,
                "user_id": user.id,
                "username": user.username,
                "full_name": user.full_name,
                "avatar_url": user.avatar_url,
                "level": user_level.level,
                "title": config.title if config else "初学者",
                "color": config.color if config else "#909399",
                "total_exp": user_level.total_exp,
                "login_streak": user_level.login_streak,
            })
        
        return leaderboard, total
    
    async def get_user_rank(self, user_id: int) -> Optional[int]:
        """
        获取用户排名
        
        Args:
            user_id: 用户ID
            
        Returns:
            排名（从1开始）
        """
        user_level = await self.get_or_create_user_level(user_id)
        
        # 计算排在该用户前面的人数
        query = select(func.count(UserLevel.id)).where(
            and_(
                UserLevel.user_id != user_id,
                (
                    (UserLevel.level > user_level.level) |
                    (
                        (UserLevel.level == user_level.level) &
                        (UserLevel.total_exp > user_level.total_exp)
                    )
                )
            )
        )
        result = await self.db.execute(query)
        count = result.scalar() or 0
        
        return count + 1
    
    async def add_exam_exp(
        self,
        user_id: int,
        exam_id: int,
        score: float,
        is_passed: bool
    ) -> Optional[Dict[str, Any]]:
        """
        考试通过获得经验值
        
        Args:
            user_id: 用户ID
            exam_id: 考试ID
            score: 得分
            is_passed: 是否通过
            
        Returns:
            经验值变化信息
        """
        if not is_passed:
            return None
        
        exp_gained = EXP_CONFIG["exam_pass"]
        bonus = []
        
        if score >= 100:
            exp_gained += EXP_CONFIG["exam_perfect"]
            bonus.append("满分")
        elif score >= 90:
            exp_gained += EXP_CONFIG["exam_excellent"]
            bonus.append("优秀")
        
        description = f"通过考试"
        if bonus:
            description += f"（{', '.join(bonus)}奖励）"
        
        _, leveled_up, new_level = await self.add_exp(
            user_id=user_id,
            exp_amount=exp_gained,
            exp_type=ExpType.EXAM,
            description=description,
            source_id=exam_id
        )
        
        return {
            "exp_gained": exp_gained,
            "leveled_up": leveled_up,
            "new_level": new_level
        }
    
    async def add_practice_exp(
        self,
        user_id: int,
        session_id: int,
        score: Optional[float] = None
    ) -> Dict[str, Any]:
        """
        完成练习获得经验值
        
        Args:
            user_id: 用户ID
            session_id: 练习会话ID
            score: 得分（可选）
            
        Returns:
            经验值变化信息
        """
        exp_gained = EXP_CONFIG["practice_complete"]
        bonus = []
        
        if score is not None and score >= 80:
            exp_gained += EXP_CONFIG["practice_good"]
            bonus.append("高分")
        
        description = f"完成练习"
        if bonus:
            description += f"（{', '.join(bonus)}奖励）"
        
        _, leveled_up, new_level = await self.add_exp(
            user_id=user_id,
            exp_amount=exp_gained,
            exp_type=ExpType.PRACTICE,
            description=description,
            source_id=session_id
        )
        
        return {
            "exp_gained": exp_gained,
            "leveled_up": leveled_up,
            "new_level": new_level
        }
    
    async def add_training_exp(
        self,
        user_id: int,
        session_id: int,
        score: Optional[float] = None
    ) -> Dict[str, Any]:
        """
        完成陪练获得经验值
        
        Args:
            user_id: 用户ID
            session_id: 陪练会话ID
            score: 得分（可选）
            
        Returns:
            经验值变化信息
        """
        exp_gained = EXP_CONFIG["training_complete"]
        bonus = []
        
        if score is not None and score >= 80:
            exp_gained += EXP_CONFIG["training_good"]
            bonus.append("高分")
        
        description = f"完成陪练"
        if bonus:
            description += f"（{', '.join(bonus)}奖励）"
        
        _, leveled_up, new_level = await self.add_exp(
            user_id=user_id,
            exp_amount=exp_gained,
            exp_type=ExpType.TRAINING,
            description=description,
            source_id=session_id
        )
        
        return {
            "exp_gained": exp_gained,
            "leveled_up": leveled_up,
            "new_level": new_level
        }
    
    async def add_task_exp(
        self,
        user_id: int,
        task_id: int
    ) -> Dict[str, Any]:
        """
        完成任务获得经验值
        
        Args:
            user_id: 用户ID
            task_id: 任务ID
            
        Returns:
            经验值变化信息
        """
        exp_gained = EXP_CONFIG["task_complete"]
        
        _, leveled_up, new_level = await self.add_exp(
            user_id=user_id,
            exp_amount=exp_gained,
            exp_type=ExpType.TASK,
            description="完成任务",
            source_id=task_id
        )
        
        return {
            "exp_gained": exp_gained,
            "leveled_up": leveled_up,
            "new_level": new_level
        }
