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, BonusAssignment, BonusAssignmentStatus, GameType,
    DisputeStatus,
)
from app.schemas import (
    SpinResult, AssignmentResponse, CompleteResult, DropResult,
    GameResponse, ChallengeResponse, GameShort, UserPublic, MessageResponse,
    TrackTimeRequest,
)
from app.schemas.game import PlaythroughInfo
from app.services.points import PointsService
from app.services.events import event_service
from app.services.storage import storage_service
from app.services.coins import coins_service
from app.services.consumables import consumables_service
from app.api.v1.games import get_available_games_for_participant

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),
            selectinload(Assignment.game).selectinload(Game.challenges),  # For playthrough
            selectinload(Assignment.bonus_assignments).selectinload(BonusAssignment.challenge),  # For playthrough bonuses
        )
        .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),
            selectinload(Assignment.game).selectinload(Game.challenges),  # For playthrough
            selectinload(Assignment.bonus_assignments).selectinload(BonusAssignment.challenge),  # For playthrough bonuses
        )
        .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 (or playthrough)"""
    # 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")

    # Get available games (filtered by completion status)
    available_games, _ = await get_available_games_for_participant(db, participant, marathon_id)

    if not available_games:
        raise HTTPException(status_code=400, detail="No games available for spin")

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

    game = None
    challenge = None
    is_playthrough = False

    # Handle special event cases (excluding Common Enemy - it has separate flow)
    # Events only apply to challenges-type games, not playthrough
    if active_event:
        if active_event.type == EventType.JACKPOT.value:
            # Jackpot: Get hard challenge only (from challenges-type games)
            challenge = await event_service.get_random_hard_challenge(db, marathon_id)
            if challenge:
                # Check if this game is available for the participant
                result = await db.execute(
                    select(Game).where(Game.id == challenge.game_id)
                )
                game = result.scalar_one_or_none()
                if game and game.id in [g.id for g in available_games]:
                    # Consume jackpot (one-time use)
                    await event_service.consume_jackpot(db, active_event.id)
                else:
                    # Game not available, fall back to normal selection
                    game = None
                    challenge = None
        # Note: Common Enemy is handled separately via event-assignment endpoints

    # Normal random selection if no special event handling
    if not game:
        game = random.choice(available_games)

        if game.game_type == GameType.PLAYTHROUGH.value:
            # Playthrough game - no challenge selection
            # Events that apply to playthrough: GOLDEN_HOUR, DOUBLE_RISK, COMMON_ENEMY
            # Events that DON'T apply: JACKPOT (hard challenges only)
            is_playthrough = True
            challenge = None
            if active_event and active_event.type == EventType.JACKPOT.value:
                active_event = None  # Jackpot doesn't apply to playthrough
        else:
            # Challenges game - select random challenge
            if not game.challenges:
                # Reload challenges if not loaded
                result = await db.execute(
                    select(Game)
                    .options(selectinload(Game.challenges))
                    .where(Game.id == game.id)
                )
                game = result.scalar_one()

            # Filter out already completed challenges
            completed_result = await db.execute(
                select(Assignment.challenge_id)
                .where(
                    Assignment.participant_id == participant.id,
                    Assignment.challenge_id.in_([c.id for c in game.challenges]),
                    Assignment.status == AssignmentStatus.COMPLETED.value,
                )
            )
            completed_ids = set(completed_result.scalars().all())
            available_challenges = [c for c in game.challenges if c.id not in completed_ids]

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

            challenge = random.choice(available_challenges)

    # Create assignment
    if is_playthrough:
        # Playthrough assignment - link to game, not challenge
        assignment = Assignment(
            participant_id=participant.id,
            game_id=game.id,
            is_playthrough=True,
            status=AssignmentStatus.ACTIVE.value,
            event_type=active_event.type if active_event else None,
        )
        db.add(assignment)
        await db.flush()  # Get assignment.id for bonus assignments

        # Create bonus assignments for all challenges
        bonus_challenges = []
        if game.challenges:
            for ch in game.challenges:
                bonus = BonusAssignment(
                    main_assignment_id=assignment.id,
                    challenge_id=ch.id,
                )
                db.add(bonus)
                bonus_challenges.append(ch)

        # Log activity
        activity_data = {
            "game": game.title,
            "is_playthrough": True,
            "points": game.playthrough_points,
            "bonus_challenges_count": len(bonus_challenges),
        }
        if active_event:
            activity_data["event_type"] = active_event.type
    else:
        # Regular challenge assignment
        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
    if is_playthrough:
        drop_penalty = points_service.calculate_drop_penalty(
            participant.drop_count, game.playthrough_points, None  # No events for playthrough
        )
    else:
        drop_penalty = points_service.calculate_drop_penalty(
            participant.drop_count, challenge.points, active_event
        )

    # Get challenges count
    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)
        )

    # Build response
    game_response = 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,
        game_type=game.game_type,
        playthrough_points=game.playthrough_points,
        playthrough_description=game.playthrough_description,
        playthrough_proof_type=game.playthrough_proof_type,
        playthrough_proof_hint=game.playthrough_proof_hint,
    )

    if is_playthrough:
        # Return playthrough result
        return SpinResult(
            assignment_id=assignment.id,
            game=game_response,
            challenge=None,
            is_playthrough=True,
            playthrough_info=PlaythroughInfo(
                description=game.playthrough_description,
                points=game.playthrough_points,
                proof_type=game.playthrough_proof_type,
                proof_hint=game.playthrough_proof_hint,
            ),
            bonus_challenges=[
                ChallengeResponse(
                    id=ch.id,
                    title=ch.title,
                    description=ch.description,
                    type=ch.type,
                    difficulty=ch.difficulty,
                    points=ch.points,
                    estimated_time=ch.estimated_time,
                    proof_type=ch.proof_type,
                    proof_hint=ch.proof_hint,
                    game=GameShort(id=game.id, title=game.title, cover_url=None, download_url=game.download_url, game_type=game.game_type),
                    is_generated=ch.is_generated,
                    created_at=ch.created_at,
                )
                for ch in bonus_challenges
            ],
            can_drop=True,
            drop_penalty=drop_penalty,
            event_type=active_event.type if active_event else None,
        )
    else:
        # Return challenge result
        return SpinResult(
            assignment_id=assignment.id,
            game=game_response,
            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, download_url=game.download_url, game_type=game.game_type),
                is_generated=challenge.is_generated,
                created_at=challenge.created_at,
            ),
            is_playthrough=False,
            can_drop=True,
            drop_penalty=drop_penalty,
            event_type=active_event.type if active_event else None,
        )


@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 no active assignment, check for returned assignments
    if not assignment:
        returned = await get_oldest_returned_assignment(db, participant.id)
        if returned:
            # Activate the returned assignment
            await activate_returned_assignment(db, returned)
            await db.commit()
            # Reload with all relationships
            assignment = await get_active_assignment(db, participant.id, is_event=False)

    if not assignment:
        return None

    # Handle playthrough assignments
    if assignment.is_playthrough:
        game = assignment.game
        # Use stored event_type for playthrough
        # All events except JACKPOT apply (DOUBLE_RISK = free drop, others affect points)
        playthrough_event = None
        if assignment.event_type and assignment.event_type != EventType.JACKPOT.value:
            class MockEvent:
                def __init__(self, event_type):
                    self.type = event_type
            playthrough_event = MockEvent(assignment.event_type)

        drop_penalty = points_service.calculate_drop_penalty(
            participant.drop_count, game.playthrough_points, playthrough_event
        )

        # Build bonus challenges response
        from app.schemas.assignment import BonusAssignmentResponse
        bonus_responses = []
        for ba in assignment.bonus_assignments:
            bonus_responses.append(BonusAssignmentResponse(
                id=ba.id,
                challenge=ChallengeResponse(
                    id=ba.challenge.id,
                    title=ba.challenge.title,
                    description=ba.challenge.description,
                    type=ba.challenge.type,
                    difficulty=ba.challenge.difficulty,
                    points=ba.challenge.points,
                    estimated_time=ba.challenge.estimated_time,
                    proof_type=ba.challenge.proof_type,
                    proof_hint=ba.challenge.proof_hint,
                    game=GameShort(id=game.id, title=game.title, cover_url=None, download_url=game.download_url, game_type=game.game_type),
                    is_generated=ba.challenge.is_generated,
                    created_at=ba.challenge.created_at,
                ),
                status=ba.status,
                proof_url=ba.proof_url,
                proof_comment=ba.proof_comment,
                points_earned=ba.points_earned,
                completed_at=ba.completed_at,
            ))

        return AssignmentResponse(
            id=assignment.id,
            challenge=None,
            game=GameShort(id=game.id, title=game.title, cover_url=None, download_url=game.download_url, game_type=game.game_type),
            is_playthrough=True,
            playthrough_info=PlaythroughInfo(
                description=game.playthrough_description,
                points=game.playthrough_points,
                proof_type=game.playthrough_proof_type,
                proof_hint=game.playthrough_proof_hint,
            ),
            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,
            bonus_challenges=bonus_responses,
            event_type=assignment.event_type,
            tracked_time_minutes=assignment.tracked_time_minutes,
        )

    # Regular challenge assignment
    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, download_url=game.download_url, game_type=game.game_type),
            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,
        event_type=assignment.event_type,
        tracked_time_minutes=assignment.tracked_time_minutes,
    )


@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),  # Legacy single file support
    proof_files: list[UploadFile] = File([]),  # Multiple files support
):
    """Complete a regular assignment with proof (not event assignments)"""
    # Get assignment with all needed relationships
    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.participant),
            selectinload(Assignment.challenge).selectinload(Challenge.game),
            selectinload(Assignment.game),  # For playthrough
            selectinload(Assignment.bonus_assignments).selectinload(BonusAssignment.challenge),  # For bonus points
            selectinload(Assignment.dispute),  # To check if it was previously disputed
        )
        .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")

    # Combine legacy single file with new multiple files
    all_files = []
    if proof_file:
        all_files.append(proof_file)
    if proof_files:
        all_files.extend(proof_files)

    # For playthrough: need either file(s) or URL or comment (proof is flexible)
    # For challenges: need either file(s) or URL
    if assignment.is_playthrough:
        if not all_files and not proof_url and not comment:
            raise HTTPException(status_code=400, detail="Proof is required (file, URL, or comment)")
    else:
        if not all_files and not proof_url:
            raise HTTPException(status_code=400, detail="Proof is required (file or URL)")

    # Handle multiple file uploads
    if all_files:
        from app.models import AssignmentProof

        for idx, file in enumerate(all_files):
            contents = await file.read()
            if len(contents) > settings.MAX_UPLOAD_SIZE:
                raise HTTPException(
                    status_code=400,
                    detail=f"File {file.filename} too large. Maximum size is {settings.MAX_UPLOAD_SIZE // 1024 // 1024} MB",
                )

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

            # Determine file type (image or video)
            file_type = "video" if ext in ["mp4", "webm", "mov", "avi"] else "image"

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

            # Create AssignmentProof record
            proof_record = AssignmentProof(
                assignment_id=assignment_id,
                file_path=file_path,
                file_type=file_type,
                order_index=idx
            )
            db.add(proof_record)

            # Legacy: set proof_path on first file for backward compatibility
            if idx == 0:
                assignment.proof_path = file_path

    # Set proof URL if provided
    if proof_url:
        assignment.proof_url = proof_url

    assignment.proof_comment = comment

    participant = assignment.participant

    # Handle playthrough completion
    if assignment.is_playthrough:
        game = assignment.game
        marathon_id = game.marathon_id

        # If tracked time exists (from desktop app), calculate points as hours * 30
        # Otherwise use admin-set playthrough_points
        if assignment.tracked_time_minutes > 0:
            hours = assignment.tracked_time_minutes / 60
            base_playthrough_points = int(hours * 30)
        else:
            base_playthrough_points = game.playthrough_points

        # Calculate BASE bonus points from completed bonus assignments (before multiplier)
        base_bonus_points = sum(
            ba.challenge.points for ba in assignment.bonus_assignments
            if ba.status == BonusAssignmentStatus.COMPLETED.value
        )

        # Total base = playthrough + all bonuses
        total_base_points = base_playthrough_points + base_bonus_points

        # Get event for playthrough (use stored event_type from assignment)
        # All events except JACKPOT apply to playthrough
        playthrough_event = None
        if assignment.event_type and assignment.event_type != EventType.JACKPOT.value:
            class MockEvent:
                def __init__(self, event_type):
                    self.type = event_type
            playthrough_event = MockEvent(assignment.event_type)

        # Apply multiplier to the TOTAL (base + bonuses), then add streak bonus
        total_points, streak_bonus, event_bonus = points_service.calculate_completion_points(
            total_base_points, participant.current_streak, playthrough_event
        )

        # Update bonus assignments to reflect multiplied points for display
        if playthrough_event:
            multiplier = points_service.EVENT_MULTIPLIERS.get(playthrough_event.type, 1.0)
            for ba in assignment.bonus_assignments:
                if ba.status == BonusAssignmentStatus.COMPLETED.value:
                    ba.points_earned = int(ba.challenge.points * multiplier)

        # Apply boost and lucky dice multipliers from consumables
        boost_multiplier = consumables_service.consume_boost_on_complete(participant)
        lucky_dice_multiplier = consumables_service.consume_lucky_dice_on_complete(participant)
        combined_multiplier = boost_multiplier * lucky_dice_multiplier
        if combined_multiplier != 1.0:
            total_points = int(total_points * combined_multiplier)

        # 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

        # Get marathon and award coins if certified
        marathon_result = await db.execute(select(Marathon).where(Marathon.id == marathon_id))
        marathon = marathon_result.scalar_one()
        coins_earned = 0
        if marathon.is_certified:
            coins_earned = await coins_service.award_playthrough_coins(
                db, current_user, participant, marathon, total_points, assignment.id
            )

        # Check if this is a redo of a previously disputed assignment
        is_redo = (
            assignment.dispute is not None and
            assignment.dispute.status == DisputeStatus.RESOLVED_INVALID.value
        )

        # Log activity
        activity_data = {
            "assignment_id": assignment.id,
            "game": game.title,
            "is_playthrough": True,
            "points": total_points,
            "base_points": base_playthrough_points,
            "bonus_points": base_bonus_points,
            "streak": participant.current_streak,
        }
        if is_redo:
            activity_data["is_redo"] = True
        if boost_multiplier > 1.0:
            activity_data["boost_multiplier"] = boost_multiplier
        if lucky_dice_multiplier != 1.0:
            activity_data["lucky_dice_multiplier"] = lucky_dice_multiplier
        if coins_earned > 0:
            activity_data["coins_earned"] = coins_earned
        if playthrough_event:
            activity_data["event_type"] = playthrough_event.type
            activity_data["event_bonus"] = event_bonus

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

        await db.commit()

        # Check for returned assignments
        returned_assignment = await get_oldest_returned_assignment(db, participant.id)
        if returned_assignment:
            await activate_returned_assignment(db, returned_assignment)
            await db.commit()

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

    # Regular challenge completion
    challenge = assignment.challenge
    marathon_id = 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)
    effective_event = active_event

    # Handle assignment-level event types (jackpot)
    if assignment.event_type == EventType.JACKPOT.value:
        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}")

    # Apply boost and lucky dice multipliers from consumables
    boost_multiplier = consumables_service.consume_boost_on_complete(participant)
    lucky_dice_multiplier = consumables_service.consume_lucky_dice_on_complete(participant)
    combined_multiplier = boost_multiplier * lucky_dice_multiplier
    if combined_multiplier != 1.0:
        total_points = int(total_points * combined_multiplier)

    # 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

    # Get marathon and award coins if certified
    marathon_result = await db.execute(select(Marathon).where(Marathon.id == marathon_id))
    marathon = marathon_result.scalar_one()
    coins_earned = 0
    if marathon.is_certified:
        coins_earned = await coins_service.award_challenge_coins(
            db, current_user, participant, marathon, challenge.difficulty, assignment.id
        )

    # Check if this is a redo of a previously disputed assignment
    is_redo = (
        assignment.dispute is not None and
        assignment.dispute.status == DisputeStatus.RESOLVED_INVALID.value
    )

    # Log activity
    activity_data = {
        "assignment_id": assignment.id,
        "game": challenge.game.title,
        "challenge": challenge.title,
        "difficulty": challenge.difficulty,
        "points": total_points,
        "streak": participant.current_streak,
    }
    if is_redo:
        activity_data["is_redo"] = True
    if boost_multiplier > 1.0:
        activity_data["boost_multiplier"] = boost_multiplier
    if lucky_dice_multiplier != 1.0:
        activity_data["lucky_dice_multiplier"] = lucky_dice_multiplier
    if coins_earned > 0:
        activity_data["coins_earned"] = coins_earned
    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
        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,
            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
    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,
        coins_earned=coins_earned,
    )


@router.patch("/assignments/{assignment_id}/track-time", response_model=MessageResponse)
async def track_assignment_time(
    assignment_id: int,
    data: TrackTimeRequest,
    current_user: CurrentUser,
    db: DbSession,
):
    """Update tracked time for an assignment (from desktop app)"""
    result = await db.execute(
        select(Assignment)
        .options(selectinload(Assignment.participant))
        .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")

    # Update tracked time (replace with new value)
    assignment.tracked_time_minutes = max(0, data.minutes)
    await db.commit()

    return MessageResponse(message=f"Tracked time updated to {data.minutes} minutes")


@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 with all needed relationships
    result = await db.execute(
        select(Assignment)
        .options(
            selectinload(Assignment.participant),
            selectinload(Assignment.challenge).selectinload(Challenge.game),
            selectinload(Assignment.game),  # For playthrough
            selectinload(Assignment.bonus_assignments),  # For resetting bonuses on drop
        )
        .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

    # Handle playthrough drop
    if assignment.is_playthrough:
        game = assignment.game
        marathon_id = game.marathon_id

        # Use stored event_type for drop penalty calculation
        # DOUBLE_RISK = free drop (0 penalty)
        playthrough_event = None
        if assignment.event_type and assignment.event_type != EventType.JACKPOT.value:
            class MockEvent:
                def __init__(self, event_type):
                    self.type = event_type
            playthrough_event = MockEvent(assignment.event_type)

        penalty = points_service.calculate_drop_penalty(
            participant.drop_count, game.playthrough_points, playthrough_event
        )

        # Save drop data for potential undo
        consumables_service.save_drop_for_undo(
            participant, penalty, participant.current_streak
        )

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

        # Reset all bonus assignments (lose any completed bonuses)
        completed_bonuses_count = 0
        for ba in assignment.bonus_assignments:
            if ba.status == BonusAssignmentStatus.COMPLETED.value:
                completed_bonuses_count += 1
            ba.status = BonusAssignmentStatus.PENDING.value
            ba.proof_path = None
            ba.proof_url = None
            ba.proof_comment = None
            ba.points_earned = 0
            ba.completed_at = None

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

        # Log activity
        activity_data = {
            "game": game.title,
            "is_playthrough": True,
            "penalty": penalty,
            "lost_bonuses": completed_bonuses_count,
        }
        if playthrough_event:
            activity_data["event_type"] = playthrough_event.type
            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,
        )

    # Regular challenge drop
    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)

    # Save drop data for potential undo
    consumables_service.save_drop_for_undo(
        participant, penalty, participant.current_streak
    )

    # 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),
            selectinload(Assignment.game),  # For playthrough
            selectinload(Assignment.bonus_assignments).selectinload(BonusAssignment.challenge),  # For playthrough bonuses
        )
        .where(Assignment.participant_id == participant.id)
        .order_by(Assignment.started_at.desc())
        .limit(limit)
        .offset(offset)
    )
    assignments = result.scalars().all()

    responses = []
    for a in assignments:
        if a.is_playthrough:
            # Playthrough assignment
            game = a.game
            from app.schemas.assignment import BonusAssignmentResponse
            bonus_responses = [
                BonusAssignmentResponse(
                    id=ba.id,
                    challenge=ChallengeResponse(
                        id=ba.challenge.id,
                        title=ba.challenge.title,
                        description=ba.challenge.description,
                        type=ba.challenge.type,
                        difficulty=ba.challenge.difficulty,
                        points=ba.challenge.points,
                        estimated_time=ba.challenge.estimated_time,
                        proof_type=ba.challenge.proof_type,
                        proof_hint=ba.challenge.proof_hint,
                        game=GameShort(id=game.id, title=game.title, cover_url=None, download_url=game.download_url, game_type=game.game_type),
                        is_generated=ba.challenge.is_generated,
                        created_at=ba.challenge.created_at,
                    ),
                    status=ba.status,
                    proof_url=ba.proof_url,
                    proof_comment=ba.proof_comment,
                    points_earned=ba.points_earned,
                    completed_at=ba.completed_at,
                )
                for ba in a.bonus_assignments
            ]

            responses.append(AssignmentResponse(
                id=a.id,
                challenge=None,
                game=GameShort(id=game.id, title=game.title, cover_url=None, download_url=game.download_url, game_type=game.game_type),
                is_playthrough=True,
                playthrough_info=PlaythroughInfo(
                    description=game.playthrough_description,
                    points=game.playthrough_points,
                    proof_type=game.playthrough_proof_type,
                    proof_hint=game.playthrough_proof_hint,
                ),
                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,
                bonus_challenges=bonus_responses,
                tracked_time_minutes=a.tracked_time_minutes,
            ))
        else:
            # Regular challenge assignment
            responses.append(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,
                        download_url=a.challenge.game.download_url,
                        game_type=a.challenge.game.game_type,
                    ),
                    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,
                tracked_time_minutes=a.tracked_time_minutes,
            ))

    return responses
