import random
from datetime import datetime
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
from sqlalchemy import select, func
from sqlalchemy.orm import selectinload

from app.api.deps import DbSession, CurrentUser
from app.core.config import settings
from app.models import (
    Marathon, MarathonStatus, Game, Challenge, Participant,
    Assignment, AssignmentStatus, Activity, ActivityType,
    EventType, Difficulty, User
)
from app.schemas import (
    SpinResult, AssignmentResponse, CompleteResult, DropResult,
    GameResponse, ChallengeResponse, GameShort, UserPublic, MessageResponse
)
from app.services.points import PointsService
from app.services.events import event_service
from app.services.storage import storage_service

router = APIRouter(tags=["wheel"])

points_service = PointsService()


async def get_participant_or_403(db, user_id: int, marathon_id: int) -> Participant:
    result = await db.execute(
        select(Participant).where(
            Participant.user_id == user_id,
            Participant.marathon_id == marathon_id,
        )
    )
    participant = result.scalar_one_or_none()
    if not participant:
        raise HTTPException(status_code=403, detail="You are not a participant of this marathon")
    return participant


async def get_active_assignment(db, participant_id: int, is_event: bool = False) -> Assignment | None:
    """Get active assignment for participant.

    Args:
        db: Database session
        participant_id: Participant ID
        is_event: If True, get event assignment (Common Enemy). If False, get regular assignment.
    """
    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.challenge).selectinload(Challenge.game)
        )
        .where(
            Assignment.participant_id == participant_id,
            Assignment.status == AssignmentStatus.ACTIVE.value,
            Assignment.is_event_assignment == is_event,
        )
    )
    return result.scalar_one_or_none()


async def get_oldest_returned_assignment(db, participant_id: int) -> Assignment | None:
    """Get the oldest returned assignment that needs to be redone."""
    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.challenge).selectinload(Challenge.game)
        )
        .where(
            Assignment.participant_id == participant_id,
            Assignment.status == AssignmentStatus.RETURNED.value,
            Assignment.is_event_assignment == False,
        )
        .order_by(Assignment.completed_at.asc())  # Oldest first
        .limit(1)
    )
    return result.scalar_one_or_none()


async def activate_returned_assignment(db, returned_assignment: Assignment) -> None:
    """
    Re-activate a returned assignment.
    Simply changes the status back to ACTIVE.
    """
    returned_assignment.status = AssignmentStatus.ACTIVE.value
    returned_assignment.started_at = datetime.utcnow()
    # Clear previous proof data for fresh attempt
    returned_assignment.proof_path = None
    returned_assignment.proof_url = None
    returned_assignment.proof_comment = None
    returned_assignment.completed_at = None
    returned_assignment.points_earned = 0


@router.post("/marathons/{marathon_id}/spin", response_model=SpinResult)
async def spin_wheel(marathon_id: int, current_user: CurrentUser, db: DbSession):
    """Spin the wheel to get a random game and challenge"""
    # Check marathon is active
    result = await db.execute(select(Marathon).where(Marathon.id == marathon_id))
    marathon = result.scalar_one_or_none()
    if not marathon:
        raise HTTPException(status_code=404, detail="Marathon not found")

    if marathon.status != MarathonStatus.ACTIVE.value:
        raise HTTPException(status_code=400, detail="Marathon is not active")

    # Check if marathon has expired by end_date
    if marathon.end_date and datetime.utcnow() > marathon.end_date:
        raise HTTPException(status_code=400, detail="Marathon has ended")

    participant = await get_participant_or_403(db, current_user.id, marathon_id)

    # Check no active regular assignment (event assignments are separate)
    active = await get_active_assignment(db, participant.id, is_event=False)
    if active:
        raise HTTPException(status_code=400, detail="You already have an active assignment")

    # Check active event
    active_event = await event_service.get_active_event(db, marathon_id)

    game = None
    challenge = None

    # Handle special event cases (excluding Common Enemy - it has separate flow)
    if active_event:
        if active_event.type == EventType.JACKPOT.value:
            # Jackpot: Get hard challenge only
            challenge = await event_service.get_random_hard_challenge(db, marathon_id)
            if challenge:
                # Load game for challenge
                result = await db.execute(
                    select(Game).where(Game.id == challenge.game_id)
                )
                game = result.scalar_one_or_none()
                # Consume jackpot (one-time use)
                await event_service.consume_jackpot(db, active_event.id)
        # Note: Common Enemy is handled separately via event-assignment endpoints

    # Normal random selection if no special event handling
    if not game or not challenge:
        result = await db.execute(
            select(Game)
            .options(selectinload(Game.challenges))
            .where(Game.marathon_id == marathon_id)
        )
        games = [g for g in result.scalars().all() if g.challenges]

        if not games:
            raise HTTPException(status_code=400, detail="No games with challenges available")

        game = random.choice(games)
        challenge = random.choice(game.challenges)

    # Create assignment (store event_type for jackpot multiplier on completion)
    assignment = Assignment(
        participant_id=participant.id,
        challenge_id=challenge.id,
        status=AssignmentStatus.ACTIVE.value,
        event_type=active_event.type if active_event else None,
    )
    db.add(assignment)

    # Log activity
    activity_data = {
        "game": game.title,
        "challenge": challenge.title,
        "difficulty": challenge.difficulty,
        "points": challenge.points,
    }
    if active_event:
        activity_data["event_type"] = active_event.type

    activity = Activity(
        marathon_id=marathon_id,
        user_id=current_user.id,
        type=ActivityType.SPIN.value,
        data=activity_data,
    )
    db.add(activity)

    await db.commit()
    await db.refresh(assignment)

    # Calculate drop penalty (considers active event for double_risk)
    drop_penalty = points_service.calculate_drop_penalty(participant.drop_count, challenge.points, active_event)

    # Get challenges count (avoid lazy loading in async context)
    challenges_count = 0
    if 'challenges' in game.__dict__:
        challenges_count = len(game.challenges)
    else:
        challenges_count = await db.scalar(
            select(func.count()).select_from(Challenge).where(Challenge.game_id == game.id)
        )

    return SpinResult(
        assignment_id=assignment.id,
        game=GameResponse(
            id=game.id,
            title=game.title,
            cover_url=storage_service.get_url(game.cover_path, "covers"),
            download_url=game.download_url,
            genre=game.genre,
            added_by=None,
            challenges_count=challenges_count,
            created_at=game.created_at,
        ),
        challenge=ChallengeResponse(
            id=challenge.id,
            title=challenge.title,
            description=challenge.description,
            type=challenge.type,
            difficulty=challenge.difficulty,
            points=challenge.points,
            estimated_time=challenge.estimated_time,
            proof_type=challenge.proof_type,
            proof_hint=challenge.proof_hint,
            game=GameShort(id=game.id, title=game.title, cover_url=None),
            is_generated=challenge.is_generated,
            created_at=challenge.created_at,
        ),
        can_drop=True,
        drop_penalty=drop_penalty,
    )


@router.get("/marathons/{marathon_id}/current-assignment", response_model=AssignmentResponse | None)
async def get_current_assignment(marathon_id: int, current_user: CurrentUser, db: DbSession):
    """Get current active regular assignment (not event assignments)"""
    participant = await get_participant_or_403(db, current_user.id, marathon_id)
    assignment = await get_active_assignment(db, participant.id, is_event=False)

    if not assignment:
        return None

    challenge = assignment.challenge
    game = challenge.game

    # Calculate drop penalty (considers active event for double_risk)
    active_event = await event_service.get_active_event(db, marathon_id)
    drop_penalty = points_service.calculate_drop_penalty(participant.drop_count, challenge.points, active_event)

    return AssignmentResponse(
        id=assignment.id,
        challenge=ChallengeResponse(
            id=challenge.id,
            title=challenge.title,
            description=challenge.description,
            type=challenge.type,
            difficulty=challenge.difficulty,
            points=challenge.points,
            estimated_time=challenge.estimated_time,
            proof_type=challenge.proof_type,
            proof_hint=challenge.proof_hint,
            game=GameShort(id=game.id, title=game.title, cover_url=None),
            is_generated=challenge.is_generated,
            created_at=challenge.created_at,
        ),
        status=assignment.status,
        proof_url=storage_service.get_url(assignment.proof_path, "proofs") if assignment.proof_path else assignment.proof_url,
        proof_comment=assignment.proof_comment,
        points_earned=assignment.points_earned,
        streak_at_completion=assignment.streak_at_completion,
        started_at=assignment.started_at,
        completed_at=assignment.completed_at,
        drop_penalty=drop_penalty,
    )


@router.post("/assignments/{assignment_id}/complete", response_model=CompleteResult)
async def complete_assignment(
    assignment_id: int,
    current_user: CurrentUser,
    db: DbSession,
    proof_url: str | None = Form(None),
    comment: str | None = Form(None),
    proof_file: UploadFile | None = File(None),
):
    """Complete a regular assignment with proof (not event assignments)"""
    # Get assignment
    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.participant),
            selectinload(Assignment.challenge),
        )
        .where(Assignment.id == assignment_id)
    )
    assignment = result.scalar_one_or_none()

    if not assignment:
        raise HTTPException(status_code=404, detail="Assignment not found")

    if assignment.participant.user_id != current_user.id:
        raise HTTPException(status_code=403, detail="This is not your assignment")

    if assignment.status != AssignmentStatus.ACTIVE.value:
        raise HTTPException(status_code=400, detail="Assignment is not active")

    # Event assignments should be completed via /event-assignments/{id}/complete
    if assignment.is_event_assignment:
        raise HTTPException(status_code=400, detail="Use /event-assignments/{id}/complete for event assignments")

    # Need either file or URL
    if not proof_file and not proof_url:
        raise HTTPException(status_code=400, detail="Proof is required (file or URL)")

    # Handle file upload
    if proof_file:
        contents = await proof_file.read()
        if len(contents) > settings.MAX_UPLOAD_SIZE:
            raise HTTPException(
                status_code=400,
                detail=f"File too large. Maximum size is {settings.MAX_UPLOAD_SIZE // 1024 // 1024} MB",
            )

        ext = proof_file.filename.split(".")[-1].lower() if proof_file.filename else "jpg"
        if ext not in settings.ALLOWED_EXTENSIONS:
            raise HTTPException(
                status_code=400,
                detail=f"Invalid file type. Allowed: {settings.ALLOWED_EXTENSIONS}",
            )

        # Upload file to storage
        filename = storage_service.generate_filename(assignment_id, proof_file.filename)
        file_path = await storage_service.upload_file(
            content=contents,
            folder="proofs",
            filename=filename,
            content_type=proof_file.content_type or "application/octet-stream",
        )

        assignment.proof_path = file_path
    else:
        assignment.proof_url = proof_url

    assignment.proof_comment = comment

    # Calculate points
    participant = assignment.participant
    challenge = assignment.challenge

    # Get marathon_id for activity and event check
    result = await db.execute(
        select(Challenge).options(selectinload(Challenge.game)).where(Challenge.id == challenge.id)
    )
    full_challenge = result.scalar_one()
    marathon_id = full_challenge.game.marathon_id

    # Check active event for point multipliers
    active_event = await event_service.get_active_event(db, marathon_id)

    # For jackpot: use the event_type stored in assignment (since event may be over)
    # For other events: use the currently active event
    effective_event = active_event

    # Handle assignment-level event types (jackpot)
    if assignment.event_type == EventType.JACKPOT.value:
        # Create a mock event object for point calculation
        class MockEvent:
            def __init__(self, event_type):
                self.type = event_type
        effective_event = MockEvent(assignment.event_type)

    total_points, streak_bonus, event_bonus = points_service.calculate_completion_points(
        challenge.points, participant.current_streak, effective_event
    )

    # Handle common enemy bonus
    common_enemy_bonus = 0
    common_enemy_closed = False
    common_enemy_winners = None
    if active_event and active_event.type == EventType.COMMON_ENEMY.value:
        common_enemy_bonus, common_enemy_closed, common_enemy_winners = await event_service.record_common_enemy_completion(
            db, active_event, participant.id, current_user.id
        )
        total_points += common_enemy_bonus
        print(f"[COMMON_ENEMY] bonus={common_enemy_bonus}, closed={common_enemy_closed}, winners={common_enemy_winners}")

    # Update assignment
    assignment.status = AssignmentStatus.COMPLETED.value
    assignment.points_earned = total_points
    assignment.streak_at_completion = participant.current_streak + 1
    assignment.completed_at = datetime.utcnow()

    # Update participant
    participant.total_points += total_points
    participant.current_streak += 1
    participant.drop_count = 0  # Reset drop counter on success

    # Log activity
    activity_data = {
        "assignment_id": assignment.id,
        "game": full_challenge.game.title,
        "challenge": challenge.title,
        "difficulty": challenge.difficulty,
        "points": total_points,
        "streak": participant.current_streak,
    }
    # Log event info (use assignment's event_type for jackpot, active_event for others)
    if assignment.event_type == EventType.JACKPOT.value:
        activity_data["event_type"] = assignment.event_type
        activity_data["event_bonus"] = event_bonus
    elif active_event:
        activity_data["event_type"] = active_event.type
        activity_data["event_bonus"] = event_bonus
    if common_enemy_bonus:
        activity_data["common_enemy_bonus"] = common_enemy_bonus

    activity = Activity(
        marathon_id=marathon_id,
        user_id=current_user.id,
        type=ActivityType.COMPLETE.value,
        data=activity_data,
    )
    db.add(activity)

    # If common enemy event auto-closed, log the event end with winners
    if common_enemy_closed and common_enemy_winners:
        from app.schemas.event import EVENT_INFO, COMMON_ENEMY_BONUSES
        # Load winner nicknames
        winner_user_ids = [w["user_id"] for w in common_enemy_winners]
        users_result = await db.execute(
            select(User).where(User.id.in_(winner_user_ids))
        )
        users_map = {u.id: u.nickname for u in users_result.scalars().all()}

        winners_data = [
            {
                "user_id": w["user_id"],
                "nickname": users_map.get(w["user_id"], "Unknown"),
                "rank": w["rank"],
                "bonus_points": COMMON_ENEMY_BONUSES.get(w["rank"], 0),
            }
            for w in common_enemy_winners
        ]
        print(f"[COMMON_ENEMY] Creating event_end activity with winners: {winners_data}")

        event_end_activity = Activity(
            marathon_id=marathon_id,
            user_id=current_user.id,  # Last completer triggers the close
            type=ActivityType.EVENT_END.value,
            data={
                "event_type": EventType.COMMON_ENEMY.value,
                "event_name": EVENT_INFO.get(EventType.COMMON_ENEMY, {}).get("name", "Общий враг"),
                "auto_closed": True,
                "winners": winners_data,
            },
        )
        db.add(event_end_activity)

    await db.commit()

    # Check for returned assignments and activate the oldest one
    returned_assignment = await get_oldest_returned_assignment(db, participant.id)
    if returned_assignment:
        await activate_returned_assignment(db, returned_assignment)
        await db.commit()
        print(f"[WHEEL] Auto-activated returned assignment {returned_assignment.id} for participant {participant.id}")

    return CompleteResult(
        points_earned=total_points,
        streak_bonus=streak_bonus,
        total_points=participant.total_points,
        new_streak=participant.current_streak,
    )


@router.post("/assignments/{assignment_id}/drop", response_model=DropResult)
async def drop_assignment(assignment_id: int, current_user: CurrentUser, db: DbSession):
    """Drop current assignment"""
    # Get assignment
    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.participant),
            selectinload(Assignment.challenge).selectinload(Challenge.game),
        )
        .where(Assignment.id == assignment_id)
    )
    assignment = result.scalar_one_or_none()

    if not assignment:
        raise HTTPException(status_code=404, detail="Assignment not found")

    if assignment.participant.user_id != current_user.id:
        raise HTTPException(status_code=403, detail="This is not your assignment")

    if assignment.status != AssignmentStatus.ACTIVE.value:
        raise HTTPException(status_code=400, detail="Assignment is not active")

    participant = assignment.participant
    marathon_id = assignment.challenge.game.marathon_id

    # Check active event for free drops (double_risk)
    active_event = await event_service.get_active_event(db, marathon_id)

    # Calculate penalty (0 if double_risk event is active)
    penalty = points_service.calculate_drop_penalty(participant.drop_count, assignment.challenge.points, active_event)

    # Update assignment
    assignment.status = AssignmentStatus.DROPPED.value
    assignment.completed_at = datetime.utcnow()

    # Update participant
    participant.total_points = max(0, participant.total_points - penalty)
    participant.current_streak = 0
    participant.drop_count += 1

    # Log activity
    activity_data = {
        "game": assignment.challenge.game.title,
        "challenge": assignment.challenge.title,
        "difficulty": assignment.challenge.difficulty,
        "penalty": penalty,
    }
    if active_event:
        activity_data["event_type"] = active_event.type
        if active_event.type == EventType.DOUBLE_RISK.value:
            activity_data["free_drop"] = True

    activity = Activity(
        marathon_id=marathon_id,
        user_id=current_user.id,
        type=ActivityType.DROP.value,
        data=activity_data,
    )
    db.add(activity)

    await db.commit()

    return DropResult(
        penalty=penalty,
        total_points=participant.total_points,
        new_drop_count=participant.drop_count,
    )


@router.get("/marathons/{marathon_id}/my-history", response_model=list[AssignmentResponse])
async def get_my_history(
    marathon_id: int,
    current_user: CurrentUser,
    db: DbSession,
    limit: int = 20,
    offset: int = 0,
):
    """Get history of user's assignments in marathon"""
    participant = await get_participant_or_403(db, current_user.id, marathon_id)

    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.challenge).selectinload(Challenge.game)
        )
        .where(Assignment.participant_id == participant.id)
        .order_by(Assignment.started_at.desc())
        .limit(limit)
        .offset(offset)
    )
    assignments = result.scalars().all()

    return [
        AssignmentResponse(
            id=a.id,
            challenge=ChallengeResponse(
                id=a.challenge.id,
                title=a.challenge.title,
                description=a.challenge.description,
                type=a.challenge.type,
                difficulty=a.challenge.difficulty,
                points=a.challenge.points,
                estimated_time=a.challenge.estimated_time,
                proof_type=a.challenge.proof_type,
                proof_hint=a.challenge.proof_hint,
                game=GameShort(
                    id=a.challenge.game.id,
                    title=a.challenge.game.title,
                    cover_url=None
                ),
                is_generated=a.challenge.is_generated,
                created_at=a.challenge.created_at,
            ),
            status=a.status,
            proof_url=storage_service.get_url(a.proof_path, "proofs") if a.proof_path else a.proof_url,
            proof_comment=a.proof_comment,
            points_earned=a.points_earned,
            streak_at_completion=a.streak_at_completion,
            started_at=a.started_at,
            completed_at=a.completed_at,
        )
        for a in assignments
    ]
