"""
Gamification API endpoints
Leaderboards, achievements, loot boxes, activity feed, etc.
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, desc, and_
from datetime import datetime, timedelta
from typing import Optional
import random
import json

from app.database import get_db
from app.models import (
    User, UserStats, Achievement, UserAchievement, LootBox, ActivityFeed,
    DailyReward, SpreadBet, SpreadBetStatus, AchievementType, LootBoxRarity,
    LootBoxRewardType, TIER_CONFIG
)
from app.routers.auth import get_current_user
from pydantic import BaseModel

router = APIRouter(prefix="/api/v1/gamification", tags=["gamification"])


# ============== Pydantic Schemas ==============

class UserStatsResponse(BaseModel):
    user_id: int
    username: str
    display_name: Optional[str]
    avatar_url: Optional[str]
    xp: int
    tier: int
    tier_name: str
    house_fee: float
    xp_to_next_tier: int
    tier_progress_percent: float
    total_wagered: float
    total_won: float
    net_profit: float
    current_win_streak: int
    best_win_streak: int
    total_bets: int
    wins: int
    losses: int
    win_rate: float
    biggest_win: float
    biggest_bet: float


class LeaderboardEntry(BaseModel):
    rank: int
    user_id: int
    username: str
    display_name: Optional[str]
    avatar_url: Optional[str]
    tier: int
    tier_name: str
    value: float  # The metric being ranked (profit, wagered, etc.)
    win_rate: float
    total_bets: int


class AchievementResponse(BaseModel):
    id: int
    type: str
    name: str
    description: str
    icon: str
    xp_reward: int
    rarity: str
    earned: bool
    earned_at: Optional[datetime]


class LootBoxResponse(BaseModel):
    id: int
    rarity: str
    source: str
    opened: bool
    reward_type: Optional[str]
    reward_value: Optional[str]
    created_at: datetime


class LootBoxOpenResult(BaseModel):
    reward_type: str
    reward_value: str
    message: str
    xp_gained: int


class ActivityFeedEntry(BaseModel):
    id: int
    user_id: int
    username: str
    display_name: Optional[str]
    activity_type: str
    message: str
    amount: Optional[float]
    created_at: datetime


class DailyRewardResponse(BaseModel):
    can_claim: bool
    current_streak: int
    reward_type: str
    reward_value: str
    next_claim_at: Optional[datetime]


class WhaleAlertEntry(BaseModel):
    user_id: int
    username: str
    display_name: Optional[str]
    bet_amount: float
    event_name: str
    spread: float
    created_at: datetime


# ============== Helper Functions ==============

async def get_or_create_user_stats(db: AsyncSession, user_id: int) -> UserStats:
    """Get or create user stats record"""
    result = await db.execute(select(UserStats).where(UserStats.user_id == user_id))
    stats = result.scalar_one_or_none()

    if not stats:
        stats = UserStats(user_id=user_id)
        db.add(stats)
        await db.commit()
        await db.refresh(stats)

    return stats


def calculate_tier(xp: int) -> int:
    """Calculate tier based on XP"""
    for tier in range(10, -1, -1):
        if xp >= TIER_CONFIG[tier][0]:
            return tier
    return 0


async def add_activity(
    db: AsyncSession,
    user_id: int,
    activity_type: str,
    message: str,
    amount: Optional[float] = None,
    metadata: Optional[dict] = None
):
    """Add an entry to the activity feed"""
    activity = ActivityFeed(
        user_id=user_id,
        activity_type=activity_type,
        message=message,
        amount=amount,
        metadata=json.dumps(metadata) if metadata else None
    )
    db.add(activity)
    await db.commit()


async def award_xp(db: AsyncSession, user_id: int, amount: int, reason: str) -> tuple[int, bool]:
    """Award XP to user, returns (new_xp, tier_changed)"""
    stats = await get_or_create_user_stats(db, user_id)
    old_tier = stats.tier
    stats.xp += amount
    new_tier = calculate_tier(stats.xp)
    tier_changed = new_tier > old_tier

    if tier_changed:
        stats.tier = new_tier
        # Award loot box for tier up
        rarity = LootBoxRarity.COMMON if new_tier < 4 else (
            LootBoxRarity.UNCOMMON if new_tier < 7 else (
                LootBoxRarity.RARE if new_tier < 9 else LootBoxRarity.EPIC
            )
        )
        loot_box = LootBox(user_id=user_id, rarity=rarity, source="tier_up")
        db.add(loot_box)

        await add_activity(
            db, user_id, "tier_up",
            f"Reached {TIER_CONFIG[new_tier][2]}! House fee now {TIER_CONFIG[new_tier][1]}%",
            metadata={"old_tier": old_tier, "new_tier": new_tier}
        )

    await db.commit()
    return stats.xp, tier_changed


# ============== Public Endpoints ==============

@router.get("/leaderboard/{category}")
async def get_leaderboard(
    category: str,
    limit: int = 20,
    db: AsyncSession = Depends(get_db)
):
    """
    Get leaderboard by category: profit, wagered, wins, win_rate, streak
    """
    valid_categories = ["profit", "wagered", "wins", "win_rate", "streak"]
    if category not in valid_categories:
        raise HTTPException(400, f"Invalid category. Must be one of: {valid_categories}")

    # Map category to column
    order_column = {
        "profit": UserStats.net_profit,
        "wagered": UserStats.total_wagered,
        "wins": User.wins,
        "win_rate": User.win_rate,
        "streak": UserStats.best_win_streak
    }[category]

    query = (
        select(User, UserStats)
        .outerjoin(UserStats, User.id == UserStats.user_id)
        .order_by(desc(order_column))
        .limit(limit)
    )

    result = await db.execute(query)
    rows = result.all()

    leaderboard = []
    for rank, (user, stats) in enumerate(rows, 1):
        tier = stats.tier if stats else 0
        value = {
            "profit": stats.net_profit if stats else 0,
            "wagered": stats.total_wagered if stats else 0,
            "wins": user.wins,
            "win_rate": user.win_rate,
            "streak": stats.best_win_streak if stats else 0
        }[category]

        leaderboard.append(LeaderboardEntry(
            rank=rank,
            user_id=user.id,
            username=user.username,
            display_name=user.display_name,
            avatar_url=user.avatar_url,
            tier=tier,
            tier_name=TIER_CONFIG[tier][2],
            value=value,
            win_rate=user.win_rate,
            total_bets=user.total_bets
        ))

    return leaderboard


@router.get("/whale-tracker")
async def get_whale_tracker(
    limit: int = 10,
    db: AsyncSession = Depends(get_db)
):
    """Get recent large bets (whale alerts)"""
    # Get bets over $500 in the last 24 hours
    whale_threshold = 500.0
    since = datetime.utcnow() - timedelta(hours=24)

    query = (
        select(SpreadBet, User)
        .join(User, SpreadBet.creator_id == User.id)
        .where(
            and_(
                SpreadBet.stake_amount >= whale_threshold,
                SpreadBet.created_at >= since
            )
        )
        .order_by(desc(SpreadBet.stake_amount))
        .limit(limit)
    )

    result = await db.execute(query)
    rows = result.all()

    whales = []
    for bet, user in rows:
        whales.append(WhaleAlertEntry(
            user_id=user.id,
            username=user.username,
            display_name=user.display_name,
            bet_amount=bet.stake_amount,
            event_name=f"Event #{bet.event_id}",  # Would need join for full name
            spread=bet.spread,
            created_at=bet.created_at
        ))

    return whales


@router.get("/activity-feed")
async def get_activity_feed(
    limit: int = 20,
    activity_type: Optional[str] = None,
    db: AsyncSession = Depends(get_db)
):
    """Get global activity feed"""
    query = (
        select(ActivityFeed, User)
        .join(User, ActivityFeed.user_id == User.id)
        .where(ActivityFeed.is_public == True)
    )

    if activity_type:
        query = query.where(ActivityFeed.activity_type == activity_type)

    query = query.order_by(desc(ActivityFeed.created_at)).limit(limit)

    result = await db.execute(query)
    rows = result.all()

    activities = []
    for activity, user in rows:
        activities.append(ActivityFeedEntry(
            id=activity.id,
            user_id=user.id,
            username=user.username,
            display_name=user.display_name,
            activity_type=activity.activity_type,
            message=activity.message,
            amount=activity.amount,
            created_at=activity.created_at
        ))

    return activities


@router.get("/recent-wins")
async def get_recent_wins(
    limit: int = 10,
    db: AsyncSession = Depends(get_db)
):
    """Get recent winning bets"""
    query = (
        select(SpreadBet, User)
        .join(User, SpreadBet.winner_id == User.id)
        .where(SpreadBet.status == SpreadBetStatus.COMPLETED)
        .order_by(desc(SpreadBet.completed_at))
        .limit(limit)
    )

    result = await db.execute(query)
    rows = result.all()

    wins = []
    for bet, user in rows:
        payout = bet.stake_amount * 2 * 0.9  # Simplified payout calculation
        wins.append({
            "user_id": user.id,
            "username": user.username,
            "display_name": user.display_name,
            "amount_won": payout,
            "stake": bet.stake_amount,
            "spread": bet.spread,
            "completed_at": bet.completed_at
        })

    return wins


@router.get("/tier-info")
async def get_tier_info():
    """Get all tier information"""
    tiers = []
    for tier, (min_xp, fee, name) in TIER_CONFIG.items():
        tiers.append({
            "tier": tier,
            "name": name,
            "min_xp": min_xp,
            "house_fee_percent": fee,
            "benefits": get_tier_benefits(tier)
        })
    return tiers


def get_tier_benefits(tier: int) -> list[str]:
    """Get benefits for a tier"""
    benefits = [f"{TIER_CONFIG[tier][1]}% house fee"]

    if tier >= 3:
        benefits.append("Priority support")
    if tier >= 5:
        benefits.append("Weekly loot box")
    if tier >= 7:
        benefits.append("Exclusive events access")
    if tier >= 9:
        benefits.append("VIP Discord channel")
    if tier == 10:
        benefits.append("Custom avatar frame")
        benefits.append("Diamond badge")

    return benefits


# ============== Authenticated Endpoints ==============

@router.get("/my-stats")
async def get_my_stats(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Get current user's gamification stats"""
    stats = await get_or_create_user_stats(db, current_user.id)

    return UserStatsResponse(
        user_id=current_user.id,
        username=current_user.username,
        display_name=current_user.display_name,
        avatar_url=current_user.avatar_url,
        xp=stats.xp,
        tier=stats.tier,
        tier_name=TIER_CONFIG[stats.tier][2],
        house_fee=TIER_CONFIG[stats.tier][1],
        xp_to_next_tier=stats.xp_to_next_tier(),
        tier_progress_percent=stats.tier_progress_percent(),
        total_wagered=stats.total_wagered,
        total_won=stats.total_won,
        net_profit=stats.net_profit,
        current_win_streak=stats.current_win_streak,
        best_win_streak=stats.best_win_streak,
        total_bets=current_user.total_bets,
        wins=current_user.wins,
        losses=current_user.losses,
        win_rate=current_user.win_rate,
        biggest_win=stats.biggest_win,
        biggest_bet=stats.biggest_bet
    )


@router.get("/my-achievements")
async def get_my_achievements(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Get user's achievements (earned and unearned)"""
    # Get all achievements
    all_achievements = await db.execute(select(Achievement))
    achievements = all_achievements.scalars().all()

    # Get user's earned achievements
    earned_query = await db.execute(
        select(UserAchievement).where(UserAchievement.user_id == current_user.id)
    )
    earned = {ua.achievement_id: ua.earned_at for ua in earned_query.scalars().all()}

    result = []
    for ach in achievements:
        result.append(AchievementResponse(
            id=ach.id,
            type=ach.type.value,
            name=ach.name,
            description=ach.description,
            icon=ach.icon,
            xp_reward=ach.xp_reward,
            rarity=ach.rarity,
            earned=ach.id in earned,
            earned_at=earned.get(ach.id)
        ))

    return result


@router.get("/my-loot-boxes")
async def get_my_loot_boxes(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Get user's loot boxes"""
    query = await db.execute(
        select(LootBox)
        .where(LootBox.user_id == current_user.id)
        .order_by(desc(LootBox.created_at))
    )
    loot_boxes = query.scalars().all()

    return [
        LootBoxResponse(
            id=lb.id,
            rarity=lb.rarity.value,
            source=lb.source,
            opened=lb.opened,
            reward_type=lb.reward_type.value if lb.reward_type else None,
            reward_value=lb.reward_value,
            created_at=lb.created_at
        )
        for lb in loot_boxes
    ]


@router.post("/open-loot-box/{loot_box_id}")
async def open_loot_box(
    loot_box_id: int,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Open a loot box"""
    result = await db.execute(
        select(LootBox).where(
            and_(LootBox.id == loot_box_id, LootBox.user_id == current_user.id)
        )
    )
    loot_box = result.scalar_one_or_none()

    if not loot_box:
        raise HTTPException(404, "Loot box not found")

    if loot_box.opened:
        raise HTTPException(400, "Loot box already opened")

    # Determine reward based on rarity
    reward = generate_loot_box_reward(loot_box.rarity)

    loot_box.opened = True
    loot_box.opened_at = datetime.utcnow()
    loot_box.reward_type = reward["type"]
    loot_box.reward_value = reward["value"]

    # Apply reward
    xp_gained = apply_loot_box_reward(reward, current_user.id, db)

    await db.commit()

    await add_activity(
        db, current_user.id, "loot_box_opened",
        f"Opened a {loot_box.rarity.value} loot box and got {reward['message']}!",
        metadata={"rarity": loot_box.rarity.value, "reward": reward}
    )

    return LootBoxOpenResult(
        reward_type=reward["type"].value,
        reward_value=reward["value"],
        message=reward["message"],
        xp_gained=xp_gained
    )


def generate_loot_box_reward(rarity: LootBoxRarity) -> dict:
    """Generate a random reward based on loot box rarity"""
    # Rarity affects reward quality
    multiplier = {
        LootBoxRarity.COMMON: 1,
        LootBoxRarity.UNCOMMON: 2,
        LootBoxRarity.RARE: 4,
        LootBoxRarity.EPIC: 8,
        LootBoxRarity.LEGENDARY: 16
    }[rarity]

    # Random reward type with weighted probabilities
    roll = random.random()

    if roll < 0.05:  # 5% nothing
        return {
            "type": LootBoxRewardType.NOTHING,
            "value": "0",
            "message": "Better luck next time!"
        }
    elif roll < 0.35:  # 30% XP boost
        xp = random.randint(50, 200) * multiplier
        return {
            "type": LootBoxRewardType.XP_BOOST,
            "value": str(xp),
            "message": f"+{xp} XP"
        }
    elif roll < 0.55:  # 20% bonus cash
        cash = random.randint(5, 25) * multiplier
        return {
            "type": LootBoxRewardType.BONUS_CASH,
            "value": str(cash),
            "message": f"${cash} bonus cash"
        }
    elif roll < 0.75:  # 20% fee reduction
        hours = random.randint(1, 6) * multiplier
        reduction = random.choice([1, 2, 3])
        return {
            "type": LootBoxRewardType.FEE_REDUCTION,
            "value": json.dumps({"hours": hours, "reduction": reduction}),
            "message": f"{reduction}% fee reduction for {hours} hours"
        }
    elif roll < 0.90:  # 15% badge
        badges = ["fire", "star", "crown", "diamond", "rocket", "trophy"]
        badge = random.choice(badges)
        return {
            "type": LootBoxRewardType.BADGE,
            "value": badge,
            "message": f"'{badge.title()}' badge"
        }
    else:  # 10% free bet
        amount = random.randint(10, 50) * multiplier
        return {
            "type": LootBoxRewardType.FREE_BET,
            "value": str(amount),
            "message": f"${amount} free bet"
        }


def apply_loot_box_reward(reward: dict, user_id: int, db: AsyncSession) -> int:
    """Apply loot box reward to user, returns XP gained"""
    reward_type = reward["type"]

    if reward_type == LootBoxRewardType.XP_BOOST:
        return int(reward["value"])
    elif reward_type == LootBoxRewardType.BONUS_CASH:
        # Would need to add to wallet - simplified for now
        return 25
    elif reward_type == LootBoxRewardType.FREE_BET:
        return 50
    elif reward_type == LootBoxRewardType.BADGE:
        return 100
    elif reward_type == LootBoxRewardType.FEE_REDUCTION:
        return 75

    return 0


@router.get("/daily-reward")
async def check_daily_reward(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Check if user can claim daily reward"""
    # Get most recent daily reward
    result = await db.execute(
        select(DailyReward)
        .where(DailyReward.user_id == current_user.id)
        .order_by(desc(DailyReward.claimed_at))
        .limit(1)
    )
    last_reward = result.scalar_one_or_none()

    now = datetime.utcnow()
    can_claim = True
    current_streak = 1
    next_claim_at = None

    if last_reward:
        hours_since = (now - last_reward.claimed_at).total_seconds() / 3600

        if hours_since < 24:
            can_claim = False
            next_claim_at = last_reward.claimed_at + timedelta(hours=24)

        if hours_since < 48:
            current_streak = last_reward.day_streak + 1
        else:
            current_streak = 1  # Reset streak

    # Determine reward based on streak
    reward_type, reward_value = get_daily_reward_for_streak(current_streak)

    return DailyRewardResponse(
        can_claim=can_claim,
        current_streak=current_streak,
        reward_type=reward_type,
        reward_value=reward_value,
        next_claim_at=next_claim_at
    )


@router.post("/claim-daily-reward")
async def claim_daily_reward(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """Claim daily login reward"""
    # Check if can claim
    check = await check_daily_reward(current_user, db)

    if not check.can_claim:
        raise HTTPException(400, "Cannot claim yet. Come back later!")

    # Create daily reward record
    reward = DailyReward(
        user_id=current_user.id,
        day_streak=check.current_streak,
        reward_type=check.reward_type,
        reward_value=check.reward_value
    )
    db.add(reward)

    # Award XP
    xp_amount = 50 + (check.current_streak * 10)
    await award_xp(db, current_user.id, xp_amount, "daily_reward")

    # Every 7 days give a loot box
    if check.current_streak % 7 == 0:
        rarity = LootBoxRarity.UNCOMMON if check.current_streak < 14 else (
            LootBoxRarity.RARE if check.current_streak < 30 else LootBoxRarity.EPIC
        )
        loot_box = LootBox(user_id=current_user.id, rarity=rarity, source="daily_streak")
        db.add(loot_box)

    await db.commit()

    await add_activity(
        db, current_user.id, "daily_reward",
        f"Claimed day {check.current_streak} reward: {check.reward_value}",
        metadata={"streak": check.current_streak}
    )

    return {
        "success": True,
        "streak": check.current_streak,
        "reward_type": check.reward_type,
        "reward_value": check.reward_value,
        "xp_gained": xp_amount,
        "bonus_loot_box": check.current_streak % 7 == 0
    }


def get_daily_reward_for_streak(streak: int) -> tuple[str, str]:
    """Get daily reward based on streak day"""
    if streak % 7 == 0:
        return ("loot_box", "Loot Box + 100 XP")
    elif streak % 3 == 0:
        return ("bonus_xp", f"{50 + streak * 10} XP")
    else:
        return ("xp", f"{25 + streak * 5} XP")
