from datetime import datetime
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from sqlalchemy import select, and_, or_
from sqlalchemy.orm import selectinload

from app.api.deps import DbSession, CurrentUser
from app.models import (
    Marathon, MarathonStatus, Participant, ParticipantRole,
    Event, EventType, Activity, ActivityType, Assignment, AssignmentStatus, Challenge, Game,
    SwapRequest as SwapRequestModel, SwapRequestStatus, User,
)
from app.models.bonus_assignment import BonusAssignment
from fastapi import UploadFile, File, Form

from app.schemas import (
    EventCreate, EventResponse, ActiveEventResponse, EventEffects,
    MessageResponse, SwapRequest, ChallengeResponse, GameShort, SwapCandidate,
    SwapRequestCreate, SwapRequestResponse, SwapRequestChallengeInfo, MySwapRequests,
    CommonEnemyLeaderboard, EventAssignmentResponse, AssignmentResponse, CompleteResult,
)
from app.core.config import settings
from app.schemas.event import EVENT_INFO, COMMON_ENEMY_BONUSES
from app.schemas.user import UserPublic
from app.services.events import event_service
from app.services.storage import storage_service

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


async def get_marathon_or_404(db, marathon_id: int) -> Marathon:
    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")
    return marathon


async def require_organizer(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="Not a participant")
    if participant.role != ParticipantRole.ORGANIZER.value:
        raise HTTPException(status_code=403, detail="Only organizers can manage events")
    return participant


async def require_participant(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="Not a participant")
    return participant


def event_to_response(event: Event) -> EventResponse:
    return EventResponse(
        id=event.id,
        type=event.type,
        start_time=event.start_time,
        end_time=event.end_time,
        is_active=event.is_active,
        created_by=UserPublic(
            id=event.created_by.id,
            login=event.created_by.login,
            nickname=event.created_by.nickname,
            avatar_url=None,
            role=event.created_by.role,
            created_at=event.created_by.created_at,
        ) if event.created_by else None,
        data=event.data,
        created_at=event.created_at,
    )


@router.get("/marathons/{marathon_id}/event", response_model=ActiveEventResponse)
async def get_active_event(
    marathon_id: int,
    current_user: CurrentUser,
    db: DbSession
):
    """Get currently active event for marathon"""
    await get_marathon_or_404(db, marathon_id)
    await require_participant(db, current_user.id, marathon_id)

    event = await event_service.get_active_event(db, marathon_id)
    effects = event_service.get_event_effects(event)
    time_remaining = event_service.get_time_remaining(event)

    return ActiveEventResponse(
        event=event_to_response(event) if event else None,
        effects=effects,
        time_remaining_seconds=time_remaining,
    )


@router.get("/marathons/{marathon_id}/events", response_model=list[EventResponse])
async def list_events(
    marathon_id: int,
    current_user: CurrentUser,
    db: DbSession,
    limit: int = 20,
    offset: int = 0,
):
    """Get event history for marathon"""
    await get_marathon_or_404(db, marathon_id)
    await require_participant(db, current_user.id, marathon_id)

    result = await db.execute(
        select(Event)
        .options(selectinload(Event.created_by))
        .where(Event.marathon_id == marathon_id)
        .order_by(Event.created_at.desc())
        .limit(limit)
        .offset(offset)
    )
    events = result.scalars().all()

    return [event_to_response(e) for e in events]


@router.post("/marathons/{marathon_id}/events", response_model=EventResponse)
async def start_event(
    marathon_id: int,
    data: EventCreate,
    current_user: CurrentUser,
    db: DbSession,
):
    """Start a new event (organizer only)"""
    marathon = await get_marathon_or_404(db, marathon_id)
    await require_organizer(db, current_user.id, marathon_id)

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

    # Validate common_enemy requires challenge_id
    if data.type == EventType.COMMON_ENEMY.value and not data.challenge_id:
        raise HTTPException(
            status_code=400,
            detail="Common enemy event requires challenge_id"
        )

    # Handle playthrough games (negative challenge_id = -game_id)
    challenge_id = data.challenge_id
    game_id = None
    is_playthrough = False

    if data.type == EventType.COMMON_ENEMY.value and challenge_id and challenge_id < 0:
        # This is a playthrough game, not a real challenge
        game_id = -challenge_id  # Convert negative to positive game_id
        challenge_id = None
        is_playthrough = True

        # Verify game exists and is a playthrough game
        from app.models.game import GameType
        result = await db.execute(
            select(Game).where(
                Game.id == game_id,
                Game.marathon_id == marathon_id,
                Game.game_type == GameType.PLAYTHROUGH.value,
            )
        )
        game = result.scalar_one_or_none()
        if not game:
            raise HTTPException(
                status_code=400,
                detail="Playthrough game not found"
            )
    elif data.type == EventType.COMMON_ENEMY.value and challenge_id and challenge_id > 0:
        # Verify regular challenge exists
        result = await db.execute(
            select(Challenge)
            .options(selectinload(Challenge.game))
            .where(Challenge.id == challenge_id)
        )
        challenge = result.scalar_one_or_none()
        if not challenge or challenge.game.marathon_id != marathon_id:
            raise HTTPException(
                status_code=400,
                detail="Challenge not found in this marathon"
            )

    try:
        event = await event_service.start_event(
            db=db,
            marathon_id=marathon_id,
            event_type=data.type,
            created_by_id=current_user.id,
            duration_minutes=data.duration_minutes,
            challenge_id=challenge_id,
            game_id=game_id,
            is_playthrough=is_playthrough,
        )
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

    # Log activity
    event_info = EVENT_INFO.get(EventType(data.type), {})
    activity = Activity(
        marathon_id=marathon_id,
        user_id=current_user.id,
        type=ActivityType.EVENT_START.value,
        data={
            "event_type": data.type,
            "event_name": event_info.get("name", data.type),
        },
    )
    db.add(activity)
    await db.commit()

    # Reload with relationship
    await db.refresh(event, ["created_by"])

    return event_to_response(event)


@router.delete("/marathons/{marathon_id}/event", response_model=MessageResponse)
async def stop_event(
    marathon_id: int,
    current_user: CurrentUser,
    db: DbSession,
):
    """Stop active event (organizer only)"""
    await get_marathon_or_404(db, marathon_id)
    await require_organizer(db, current_user.id, marathon_id)

    event = await event_service.get_active_event(db, marathon_id)
    if not event:
        raise HTTPException(status_code=404, detail="No active event")

    # Build activity data before ending event
    event_info = EVENT_INFO.get(EventType(event.type), {})
    activity_data = {
        "event_type": event.type,
        "event_name": event_info.get("name", event.type),
        "auto_closed": False,
    }

    # For common_enemy, include winners in activity
    if event.type == EventType.COMMON_ENEMY.value:
        event_data = event.data or {}
        completions = event_data.get("completions", [])
        if completions:
            activity_data["winners"] = [
                {
                    "user_id": c["user_id"],
                    "rank": c["rank"],
                    "bonus_points": COMMON_ENEMY_BONUSES.get(c["rank"], 0),
                }
                for c in completions[:3]  # Top 3
            ]

    await event_service.end_event(db, event.id)

    # Log activity
    activity = Activity(
        marathon_id=marathon_id,
        user_id=current_user.id,
        type=ActivityType.EVENT_END.value,
        data=activity_data,
    )
    db.add(activity)
    await db.commit()

    return MessageResponse(message="Event stopped")


def build_assignment_info(assignment: Assignment) -> SwapRequestChallengeInfo:
    """Build SwapRequestChallengeInfo from assignment (challenge or playthrough)"""
    if assignment.is_playthrough:
        return SwapRequestChallengeInfo(
            is_playthrough=True,
            playthrough_description=assignment.game.playthrough_description,
            playthrough_points=assignment.game.playthrough_points,
            game_title=assignment.game.title,
        )
    else:
        return SwapRequestChallengeInfo(
            is_playthrough=False,
            title=assignment.challenge.title,
            description=assignment.challenge.description,
            points=assignment.challenge.points,
            difficulty=assignment.challenge.difficulty,
            game_title=assignment.challenge.game.title,
        )


def build_swap_request_response(
    swap_req: SwapRequestModel,
) -> SwapRequestResponse:
    """Build SwapRequestResponse from model with loaded relationships"""
    return SwapRequestResponse(
        id=swap_req.id,
        status=swap_req.status,
        from_user=UserPublic(
            id=swap_req.from_participant.user.id,
            login=swap_req.from_participant.user.login,
            nickname=swap_req.from_participant.user.nickname,
            avatar_url=None,
            role=swap_req.from_participant.user.role,
            created_at=swap_req.from_participant.user.created_at,
        ),
        to_user=UserPublic(
            id=swap_req.to_participant.user.id,
            login=swap_req.to_participant.user.login,
            nickname=swap_req.to_participant.user.nickname,
            avatar_url=None,
            role=swap_req.to_participant.user.role,
            created_at=swap_req.to_participant.user.created_at,
        ),
        from_challenge=build_assignment_info(swap_req.from_assignment),
        to_challenge=build_assignment_info(swap_req.to_assignment),
        created_at=swap_req.created_at,
        responded_at=swap_req.responded_at,
    )


@router.post("/marathons/{marathon_id}/swap-requests", response_model=SwapRequestResponse)
async def create_swap_request(
    marathon_id: int,
    data: SwapRequestCreate,
    current_user: CurrentUser,
    db: DbSession,
):
    """Create a swap request to another participant (requires their confirmation)"""
    await get_marathon_or_404(db, marathon_id)
    participant = await require_participant(db, current_user.id, marathon_id)

    # Check active swap event
    event = await event_service.get_active_event(db, marathon_id)
    if not event or event.type != EventType.SWAP.value:
        raise HTTPException(status_code=400, detail="No active swap event")

    # Get target participant
    result = await db.execute(
        select(Participant)
        .options(selectinload(Participant.user))
        .where(
            Participant.id == data.target_participant_id,
            Participant.marathon_id == marathon_id,
        )
    )
    target = result.scalar_one_or_none()
    if not target:
        raise HTTPException(status_code=404, detail="Target participant not found")

    if target.id == participant.id:
        raise HTTPException(status_code=400, detail="Cannot swap with yourself")

    # Get both active assignments (with challenge.game or game for playthrough)
    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.challenge).selectinload(Challenge.game),
            selectinload(Assignment.game),  # For playthrough
        )
        .where(
            Assignment.participant_id == participant.id,
            Assignment.status == AssignmentStatus.ACTIVE.value,
        )
    )
    my_assignment = result.scalar_one_or_none()

    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.challenge).selectinload(Challenge.game),
            selectinload(Assignment.game),  # For playthrough
        )
        .where(
            Assignment.participant_id == target.id,
            Assignment.status == AssignmentStatus.ACTIVE.value,
        )
    )
    target_assignment = result.scalar_one_or_none()

    if not my_assignment or not target_assignment:
        raise HTTPException(
            status_code=400,
            detail="Both participants must have active assignments to swap"
        )

    # Check if there's already a pending request between these participants
    result = await db.execute(
        select(SwapRequestModel).where(
            SwapRequestModel.event_id == event.id,
            SwapRequestModel.status == SwapRequestStatus.PENDING.value,
            or_(
                and_(
                    SwapRequestModel.from_participant_id == participant.id,
                    SwapRequestModel.to_participant_id == target.id,
                ),
                and_(
                    SwapRequestModel.from_participant_id == target.id,
                    SwapRequestModel.to_participant_id == participant.id,
                ),
            )
        )
    )
    existing = result.scalar_one_or_none()
    if existing:
        raise HTTPException(
            status_code=400,
            detail="A pending swap request already exists between you and this participant"
        )

    # Create swap request
    swap_request = SwapRequestModel(
        event_id=event.id,
        from_participant_id=participant.id,
        to_participant_id=target.id,
        from_assignment_id=my_assignment.id,
        to_assignment_id=target_assignment.id,
        status=SwapRequestStatus.PENDING.value,
    )
    db.add(swap_request)
    await db.commit()
    await db.refresh(swap_request)

    # Load relationships for response (including game for playthrough)
    result = await db.execute(
        select(SwapRequestModel)
        .options(
            selectinload(SwapRequestModel.from_participant).selectinload(Participant.user),
            selectinload(SwapRequestModel.to_participant).selectinload(Participant.user),
            selectinload(SwapRequestModel.from_assignment)
                .selectinload(Assignment.challenge)
                .selectinload(Challenge.game),
            selectinload(SwapRequestModel.from_assignment)
                .selectinload(Assignment.game),
            selectinload(SwapRequestModel.to_assignment)
                .selectinload(Assignment.challenge)
                .selectinload(Challenge.game),
            selectinload(SwapRequestModel.to_assignment)
                .selectinload(Assignment.game),
        )
        .where(SwapRequestModel.id == swap_request.id)
    )
    swap_request = result.scalar_one()

    return build_swap_request_response(swap_request)


@router.get("/marathons/{marathon_id}/swap-requests", response_model=MySwapRequests)
async def get_my_swap_requests(
    marathon_id: int,
    current_user: CurrentUser,
    db: DbSession,
):
    """Get current user's incoming and outgoing swap requests"""
    await get_marathon_or_404(db, marathon_id)
    participant = await require_participant(db, current_user.id, marathon_id)

    # Check active swap event
    event = await event_service.get_active_event(db, marathon_id)
    if not event or event.type != EventType.SWAP.value:
        return MySwapRequests(incoming=[], outgoing=[])

    # Get all pending requests for this event involving this participant
    result = await db.execute(
        select(SwapRequestModel)
        .options(
            selectinload(SwapRequestModel.from_participant).selectinload(Participant.user),
            selectinload(SwapRequestModel.to_participant).selectinload(Participant.user),
            selectinload(SwapRequestModel.from_assignment)
                .selectinload(Assignment.challenge)
                .selectinload(Challenge.game),
            selectinload(SwapRequestModel.from_assignment)
                .selectinload(Assignment.game),
            selectinload(SwapRequestModel.to_assignment)
                .selectinload(Assignment.challenge)
                .selectinload(Challenge.game),
            selectinload(SwapRequestModel.to_assignment)
                .selectinload(Assignment.game),
        )
        .where(
            SwapRequestModel.event_id == event.id,
            SwapRequestModel.status == SwapRequestStatus.PENDING.value,
            or_(
                SwapRequestModel.from_participant_id == participant.id,
                SwapRequestModel.to_participant_id == participant.id,
            )
        )
        .order_by(SwapRequestModel.created_at.desc())
    )
    requests = result.scalars().all()

    incoming = []
    outgoing = []
    for req in requests:
        response = build_swap_request_response(req)
        if req.to_participant_id == participant.id:
            incoming.append(response)
        else:
            outgoing.append(response)

    return MySwapRequests(incoming=incoming, outgoing=outgoing)


@router.post("/marathons/{marathon_id}/swap-requests/{request_id}/accept", response_model=MessageResponse)
async def accept_swap_request(
    marathon_id: int,
    request_id: int,
    current_user: CurrentUser,
    db: DbSession,
):
    """Accept a swap request (performs the actual swap)"""
    await get_marathon_or_404(db, marathon_id)
    participant = await require_participant(db, current_user.id, marathon_id)

    # Check active swap event
    event = await event_service.get_active_event(db, marathon_id)
    if not event or event.type != EventType.SWAP.value:
        raise HTTPException(status_code=400, detail="No active swap event")

    # Get the swap request
    result = await db.execute(
        select(SwapRequestModel)
        .options(
            selectinload(SwapRequestModel.from_participant).selectinload(Participant.user),
            selectinload(SwapRequestModel.to_participant),
            selectinload(SwapRequestModel.from_assignment),
            selectinload(SwapRequestModel.to_assignment),
        )
        .where(
            SwapRequestModel.id == request_id,
            SwapRequestModel.event_id == event.id,
        )
    )
    swap_request = result.scalar_one_or_none()
    if not swap_request:
        raise HTTPException(status_code=404, detail="Swap request not found")

    # Check that current user is the target
    if swap_request.to_participant_id != participant.id:
        raise HTTPException(status_code=403, detail="You can only accept requests sent to you")

    if swap_request.status != SwapRequestStatus.PENDING.value:
        raise HTTPException(status_code=400, detail="This request is no longer pending")

    # Verify both assignments are still active
    result = await db.execute(
        select(Assignment).where(Assignment.id == swap_request.from_assignment_id)
    )
    from_assignment = result.scalar_one_or_none()

    result = await db.execute(
        select(Assignment).where(Assignment.id == swap_request.to_assignment_id)
    )
    to_assignment = result.scalar_one_or_none()

    if not from_assignment or not to_assignment:
        swap_request.status = SwapRequestStatus.CANCELLED.value
        swap_request.responded_at = datetime.utcnow()
        await db.commit()
        raise HTTPException(status_code=400, detail="One or both assignments no longer exist")

    if from_assignment.status != AssignmentStatus.ACTIVE.value or to_assignment.status != AssignmentStatus.ACTIVE.value:
        swap_request.status = SwapRequestStatus.CANCELLED.value
        swap_request.responded_at = datetime.utcnow()
        await db.commit()
        raise HTTPException(status_code=400, detail="One or both assignments are no longer active")

    # Perform the swap (swap challenge_id, game_id, and is_playthrough)
    from_challenge_id = from_assignment.challenge_id
    from_game_id = from_assignment.game_id
    from_is_playthrough = from_assignment.is_playthrough

    from_assignment.challenge_id = to_assignment.challenge_id
    from_assignment.game_id = to_assignment.game_id
    from_assignment.is_playthrough = to_assignment.is_playthrough

    to_assignment.challenge_id = from_challenge_id
    to_assignment.game_id = from_game_id
    to_assignment.is_playthrough = from_is_playthrough

    # Swap bonus assignments between the two assignments
    from sqlalchemy import update as sql_update

    # Get bonus assignments for both
    from_bonus_result = await db.execute(
        select(BonusAssignment).where(BonusAssignment.main_assignment_id == from_assignment.id)
    )
    from_bonus_assignments = from_bonus_result.scalars().all()

    to_bonus_result = await db.execute(
        select(BonusAssignment).where(BonusAssignment.main_assignment_id == to_assignment.id)
    )
    to_bonus_assignments = to_bonus_result.scalars().all()

    # Move bonus assignments: from -> to, to -> from
    for bonus in from_bonus_assignments:
        bonus.main_assignment_id = to_assignment.id

    for bonus in to_bonus_assignments:
        bonus.main_assignment_id = from_assignment.id

    # Update request status
    swap_request.status = SwapRequestStatus.ACCEPTED.value
    swap_request.responded_at = datetime.utcnow()

    # Cancel any other pending requests involving these participants in this event
    await db.execute(
        select(SwapRequestModel)
        .where(
            SwapRequestModel.event_id == event.id,
            SwapRequestModel.status == SwapRequestStatus.PENDING.value,
            SwapRequestModel.id != request_id,
            or_(
                SwapRequestModel.from_participant_id.in_([swap_request.from_participant_id, swap_request.to_participant_id]),
                SwapRequestModel.to_participant_id.in_([swap_request.from_participant_id, swap_request.to_participant_id]),
            )
        )
    )
    # Update those to cancelled
    from sqlalchemy import update
    await db.execute(
        update(SwapRequestModel)
        .where(
            SwapRequestModel.event_id == event.id,
            SwapRequestModel.status == SwapRequestStatus.PENDING.value,
            SwapRequestModel.id != request_id,
            or_(
                SwapRequestModel.from_participant_id.in_([swap_request.from_participant_id, swap_request.to_participant_id]),
                SwapRequestModel.to_participant_id.in_([swap_request.from_participant_id, swap_request.to_participant_id]),
            )
        )
        .values(status=SwapRequestStatus.CANCELLED.value, responded_at=datetime.utcnow())
    )

    # Log activity
    activity = Activity(
        marathon_id=marathon_id,
        user_id=current_user.id,
        type=ActivityType.SWAP.value,
        data={
            "swapped_with_user_id": swap_request.from_participant.user_id,
            "swapped_with_nickname": swap_request.from_participant.user.nickname,
        },
    )
    db.add(activity)

    await db.commit()

    return MessageResponse(message="Swap completed successfully!")


@router.post("/marathons/{marathon_id}/swap-requests/{request_id}/decline", response_model=MessageResponse)
async def decline_swap_request(
    marathon_id: int,
    request_id: int,
    current_user: CurrentUser,
    db: DbSession,
):
    """Decline a swap request"""
    await get_marathon_or_404(db, marathon_id)
    participant = await require_participant(db, current_user.id, marathon_id)

    # Check active swap event (allow declining even if event ended)
    event = await event_service.get_active_event(db, marathon_id)

    # Get the swap request
    result = await db.execute(
        select(SwapRequestModel).where(SwapRequestModel.id == request_id)
    )
    swap_request = result.scalar_one_or_none()
    if not swap_request:
        raise HTTPException(status_code=404, detail="Swap request not found")

    # Check that current user is the target
    if swap_request.to_participant_id != participant.id:
        raise HTTPException(status_code=403, detail="You can only decline requests sent to you")

    if swap_request.status != SwapRequestStatus.PENDING.value:
        raise HTTPException(status_code=400, detail="This request is no longer pending")

    # Update status
    swap_request.status = SwapRequestStatus.DECLINED.value
    swap_request.responded_at = datetime.utcnow()

    await db.commit()

    return MessageResponse(message="Swap request declined")


@router.delete("/marathons/{marathon_id}/swap-requests/{request_id}", response_model=MessageResponse)
async def cancel_swap_request(
    marathon_id: int,
    request_id: int,
    current_user: CurrentUser,
    db: DbSession,
):
    """Cancel your own outgoing swap request"""
    await get_marathon_or_404(db, marathon_id)
    participant = await require_participant(db, current_user.id, marathon_id)

    # Get the swap request
    result = await db.execute(
        select(SwapRequestModel).where(SwapRequestModel.id == request_id)
    )
    swap_request = result.scalar_one_or_none()
    if not swap_request:
        raise HTTPException(status_code=404, detail="Swap request not found")

    # Check that current user is the sender
    if swap_request.from_participant_id != participant.id:
        raise HTTPException(status_code=403, detail="You can only cancel your own requests")

    if swap_request.status != SwapRequestStatus.PENDING.value:
        raise HTTPException(status_code=400, detail="This request is no longer pending")

    # Update status
    swap_request.status = SwapRequestStatus.CANCELLED.value
    swap_request.responded_at = datetime.utcnow()

    await db.commit()

    return MessageResponse(message="Swap request cancelled")


# ==================== Game Choice Event Endpoints ====================


class GameChoiceChallengeResponse(BaseModel):
    """Challenge option for game choice event"""
    id: int
    title: str
    description: str
    difficulty: str
    points: int
    estimated_time: int | None
    proof_type: str
    proof_hint: str | None


class GameChoiceChallengesResponse(BaseModel):
    """Response with available challenges for game choice"""
    game_id: int
    game_title: str
    challenges: list[GameChoiceChallengeResponse]


class GameChoiceSelectRequest(BaseModel):
    """Request to select a challenge during game choice event"""
    challenge_id: int


@router.get("/marathons/{marathon_id}/game-choice/challenges", response_model=GameChoiceChallengesResponse)
async def get_game_choice_challenges(
    marathon_id: int,
    game_id: int,
    current_user: CurrentUser,
    db: DbSession,
):
    """Get 3 random challenges from a game for game choice event"""
    from app.models import Game
    from sqlalchemy.sql.expression import func

    await get_marathon_or_404(db, marathon_id)
    participant = await require_participant(db, current_user.id, marathon_id)

    # Check active game_choice event
    event = await event_service.get_active_event(db, marathon_id)
    if not event or event.type != EventType.GAME_CHOICE.value:
        raise HTTPException(status_code=400, detail="No active game choice event")

    # Get the game
    result = await db.execute(
        select(Game).where(Game.id == game_id, Game.marathon_id == marathon_id)
    )
    game = result.scalar_one_or_none()
    if not game:
        raise HTTPException(status_code=404, detail="Game not found")

    # Get 3 random challenges from this game
    result = await db.execute(
        select(Challenge)
        .where(Challenge.game_id == game_id)
        .order_by(func.random())
        .limit(3)
    )
    challenges = result.scalars().all()

    if not challenges:
        raise HTTPException(status_code=400, detail="No challenges available for this game")

    return GameChoiceChallengesResponse(
        game_id=game.id,
        game_title=game.title,
        challenges=[
            GameChoiceChallengeResponse(
                id=c.id,
                title=c.title,
                description=c.description,
                difficulty=c.difficulty,
                points=c.points,
                estimated_time=c.estimated_time,
                proof_type=c.proof_type,
                proof_hint=c.proof_hint,
            )
            for c in challenges
        ],
    )


@router.post("/marathons/{marathon_id}/game-choice/select", response_model=MessageResponse)
async def select_game_choice_challenge(
    marathon_id: int,
    data: GameChoiceSelectRequest,
    current_user: CurrentUser,
    db: DbSession,
):
    """Select a challenge during game choice event (replaces current assignment if any)"""
    await get_marathon_or_404(db, marathon_id)
    participant = await require_participant(db, current_user.id, marathon_id)

    # Check active game_choice event
    event = await event_service.get_active_event(db, marathon_id)
    if not event or event.type != EventType.GAME_CHOICE.value:
        raise HTTPException(status_code=400, detail="No active game choice event")

    # Get the challenge
    result = await db.execute(
        select(Challenge)
        .options(selectinload(Challenge.game))
        .where(Challenge.id == data.challenge_id)
    )
    challenge = result.scalar_one_or_none()
    if not challenge:
        raise HTTPException(status_code=404, detail="Challenge not found")

    # Verify challenge belongs to this marathon
    if challenge.game.marathon_id != marathon_id:
        raise HTTPException(status_code=400, detail="Challenge does not belong to this marathon")

    # Check for current active assignment (non-event)
    result = await db.execute(
        select(Assignment)
        .options(selectinload(Assignment.challenge))
        .where(
            Assignment.participant_id == participant.id,
            Assignment.status == AssignmentStatus.ACTIVE.value,
            Assignment.is_event_assignment == False,
        )
    )
    current_assignment = result.scalar_one_or_none()

    # If there's a current assignment, replace it (free drop during this event)
    old_challenge_title = None
    if current_assignment:
        old_challenge_title = current_assignment.challenge.title
        # Mark old assignment as dropped (no penalty during game_choice event)
        current_assignment.status = AssignmentStatus.DROPPED.value
        current_assignment.completed_at = datetime.utcnow()

    # Create new assignment with chosen challenge
    new_assignment = Assignment(
        participant_id=participant.id,
        challenge_id=data.challenge_id,
        status=AssignmentStatus.ACTIVE.value,
        event_type=EventType.GAME_CHOICE.value,
    )
    db.add(new_assignment)

    # Log activity
    activity_data = {
        "game": challenge.game.title,
        "challenge": challenge.title,
        "event_type": EventType.GAME_CHOICE.value,
    }
    if old_challenge_title:
        activity_data["replaced_challenge"] = old_challenge_title

    activity = Activity(
        marathon_id=marathon_id,
        user_id=current_user.id,
        type=ActivityType.SPIN.value,  # Treat as a spin activity
        data=activity_data,
    )
    db.add(activity)

    await db.commit()

    if old_challenge_title:
        return MessageResponse(message=f"Задание заменено! Теперь у вас: {challenge.title}")
    else:
        return MessageResponse(message=f"Задание выбрано: {challenge.title}")


@router.get("/marathons/{marathon_id}/swap-candidates", response_model=list[SwapCandidate])
async def get_swap_candidates(
    marathon_id: int,
    current_user: CurrentUser,
    db: DbSession,
):
    """Get participants with active assignments available for swap (during swap event)"""
    await get_marathon_or_404(db, marathon_id)
    participant = await require_participant(db, current_user.id, marathon_id)

    # Check active swap event
    event = await event_service.get_active_event(db, marathon_id)
    if not event or event.type != EventType.SWAP.value:
        raise HTTPException(status_code=400, detail="No active swap event")

    from app.models import Game
    candidates = []

    # Get challenge-based assignments
    result = await db.execute(
        select(Participant, Assignment, Challenge, Game)
        .join(Assignment, Assignment.participant_id == Participant.id)
        .join(Challenge, Assignment.challenge_id == Challenge.id)
        .join(Game, Challenge.game_id == Game.id)
        .options(selectinload(Participant.user))
        .where(
            Participant.marathon_id == marathon_id,
            Participant.id != participant.id,
            Assignment.status == AssignmentStatus.ACTIVE.value,
            Assignment.is_playthrough == False,
        )
    )
    for p, assignment, challenge, game in result.all():
        candidates.append(SwapCandidate(
            participant_id=p.id,
            user=UserPublic(
                id=p.user.id,
                login=p.user.login,
                nickname=p.user.nickname,
                avatar_url=None,
                role=p.user.role,
                created_at=p.user.created_at,
            ),
            is_playthrough=False,
            challenge_title=challenge.title,
            challenge_description=challenge.description,
            challenge_points=challenge.points,
            challenge_difficulty=challenge.difficulty,
            game_title=game.title,
        ))

    # Get playthrough-based assignments
    result = await db.execute(
        select(Participant, Assignment, Game)
        .join(Assignment, Assignment.participant_id == Participant.id)
        .join(Game, Assignment.game_id == Game.id)
        .options(selectinload(Participant.user))
        .where(
            Participant.marathon_id == marathon_id,
            Participant.id != participant.id,
            Assignment.status == AssignmentStatus.ACTIVE.value,
            Assignment.is_playthrough == True,
        )
    )
    for p, assignment, game in result.all():
        candidates.append(SwapCandidate(
            participant_id=p.id,
            user=UserPublic(
                id=p.user.id,
                login=p.user.login,
                nickname=p.user.nickname,
                avatar_url=None,
                role=p.user.role,
                created_at=p.user.created_at,
            ),
            is_playthrough=True,
            playthrough_description=game.playthrough_description,
            playthrough_points=game.playthrough_points,
            game_title=game.title,
        ))

    return candidates


@router.get("/marathons/{marathon_id}/common-enemy-leaderboard", response_model=list[CommonEnemyLeaderboard])
async def get_common_enemy_leaderboard(
    marathon_id: int,
    current_user: CurrentUser,
    db: DbSession,
):
    """Get leaderboard for common enemy event (who completed the challenge)"""
    await get_marathon_or_404(db, marathon_id)
    await require_participant(db, current_user.id, marathon_id)

    # Get active common enemy event
    event = await event_service.get_active_event(db, marathon_id)
    if not event or event.type != EventType.COMMON_ENEMY.value:
        return []

    # Get completions from event data
    data = event.data or {}
    completions = data.get("completions", [])

    if not completions:
        return []

    # Get user info for all participants who completed
    user_ids = [c["user_id"] for c in completions]
    result = await db.execute(
        select(User).where(User.id.in_(user_ids))
    )
    users_by_id = {u.id: u for u in result.scalars().all()}

    # Build leaderboard
    leaderboard = []
    for completion in completions:
        user = users_by_id.get(completion["user_id"])
        if user:
            leaderboard.append(
                CommonEnemyLeaderboard(
                    participant_id=completion["participant_id"],
                    user=UserPublic(
                        id=user.id,
                        login=user.login,
                        nickname=user.nickname,
                        avatar_url=None,
                        role=user.role,
                        created_at=user.created_at,
                    ),
                    completed_at=completion.get("completed_at"),
                    rank=completion.get("rank"),
                    bonus_points=COMMON_ENEMY_BONUSES.get(completion.get("rank", 0), 0),
                )
            )

    return leaderboard


# ==================== Event Assignment Endpoints ====================


def assignment_to_response(assignment: Assignment) -> AssignmentResponse:
    """Convert Assignment model to AssignmentResponse"""
    # Handle playthrough assignments (no challenge, only game)
    if assignment.is_playthrough and assignment.game:
        game = assignment.game
        return AssignmentResponse(
            id=assignment.id,
            challenge=ChallengeResponse(
                id=-game.id,  # Negative ID for playthrough
                title=f"Прохождение: {game.title}",
                description=game.playthrough_description or "Пройдите игру",
                type="completion",
                difficulty="medium",
                points=game.playthrough_points or 0,
                estimated_time=None,
                proof_type=game.playthrough_proof_type or "screenshot",
                proof_hint=game.playthrough_proof_hint,
                game=GameShort(
                    id=game.id,
                    title=game.title,
                    cover_url=storage_service.get_url(game.cover_path, "covers"),
                    download_url=game.download_url,
                    game_type=game.game_type,
                ),
                is_generated=False,
                created_at=game.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,
            tracked_time_minutes=assignment.tracked_time_minutes,
        )

    # Regular challenge assignment
    challenge = assignment.challenge
    game = challenge.game
    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=storage_service.get_url(game.cover_path, "covers"),
                download_url=game.download_url,
            ),
            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,
        tracked_time_minutes=assignment.tracked_time_minutes,
    )


@router.get("/marathons/{marathon_id}/event-assignment", response_model=EventAssignmentResponse)
async def get_event_assignment(
    marathon_id: int,
    current_user: CurrentUser,
    db: DbSession,
):
    """Get current user's event assignment (Common Enemy)"""
    await get_marathon_or_404(db, marathon_id)
    participant = await require_participant(db, current_user.id, marathon_id)

    # Get active common enemy event
    event = await event_service.get_active_event(db, marathon_id)

    # Find event assignment for this participant
    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.challenge).selectinload(Challenge.game),
            selectinload(Assignment.game),  # For playthrough assignments
        )
        .where(
            Assignment.participant_id == participant.id,
            Assignment.is_event_assignment == True,
        )
        .order_by(Assignment.started_at.desc())
    )
    assignment = result.scalar_one_or_none()

    # Check if completed
    is_completed = assignment.status == AssignmentStatus.COMPLETED.value if assignment else False

    # If no active event but we have an assignment, it might be from a past event
    # Only return it if the event is still active
    if not event or event.type != EventType.COMMON_ENEMY.value:
        # Check if assignment belongs to an inactive event
        if assignment and assignment.event_id:
            result = await db.execute(
                select(Event).where(Event.id == assignment.event_id)
            )
            assignment_event = result.scalar_one_or_none()
            if assignment_event and not assignment_event.is_active:
                # Event ended, don't return the assignment
                return EventAssignmentResponse(
                    assignment=None,
                    event_id=None,
                    challenge_id=None,
                    is_completed=False,
                )

    # Determine challenge_id for response (negative for playthrough)
    challenge_id_response = None
    if event and event.data:
        if event.data.get("is_playthrough"):
            game_id = event.data.get("game_id")
            challenge_id_response = -game_id if game_id else None
        else:
            challenge_id_response = event.data.get("challenge_id")

    return EventAssignmentResponse(
        assignment=assignment_to_response(assignment) if assignment else None,
        event_id=event.id if event else None,
        challenge_id=challenge_id_response,
        is_completed=is_completed,
    )


@router.post("/event-assignments/{assignment_id}/complete", response_model=CompleteResult)
async def complete_event_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 an event assignment (Common Enemy) with proof"""
    from app.services.points import PointsService
    points_service = PointsService()

    # Get assignment
    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.participant),
            selectinload(Assignment.challenge).selectinload(Challenge.game),
            selectinload(Assignment.game),  # For playthrough assignments
        )
        .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")

    # Must be event assignment
    if not assignment.is_event_assignment:
        raise HTTPException(status_code=400, detail="This is not an event assignment")

    # 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

    # Get marathon_id and base points (handle playthrough vs regular challenge)
    participant = assignment.participant
    if assignment.is_playthrough and assignment.game:
        marathon_id = assignment.game.marathon_id
        base_points = assignment.game.playthrough_points or 0
        challenge_title = f"Прохождение: {assignment.game.title}"
        game_title = assignment.game.title
        difficulty = "medium"
    else:
        challenge = assignment.challenge
        marathon_id = challenge.game.marathon_id
        base_points = challenge.points
        challenge_title = challenge.title
        game_title = challenge.game.title
        difficulty = challenge.difficulty

    # Get active event for bonus calculation
    active_event = await event_service.get_active_event(db, marathon_id)

    # 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 = base_points + common_enemy_bonus

    # Update assignment
    assignment.status = AssignmentStatus.COMPLETED.value
    assignment.points_earned = total_points
    assignment.completed_at = datetime.utcnow()

    # Update participant points (event assignments add to total but don't affect streak)
    participant.total_points += total_points

    # Log activity
    activity_data = {
        "assignment_id": assignment.id,
        "game": game_title,
        "challenge": challenge_title,
        "difficulty": difficulty,
        "points": total_points,
        "event_type": EventType.COMMON_ENEMY.value,
        "is_event_assignment": True,
        "is_playthrough": assignment.is_playthrough,
    }
    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:
        # 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
        ]

        event_end_activity = Activity(
            marathon_id=marathon_id,
            user_id=current_user.id,
            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()

    return CompleteResult(
        points_earned=total_points,
        streak_bonus=0,  # Event assignments don't give streak bonus
        total_points=participant.total_points,
        new_streak=participant.current_streak,  # Streak unchanged
    )
