from fastapi import APIRouter, HTTPException, status, UploadFile, File, Query
from sqlalchemy import select, func
from sqlalchemy.orm import selectinload

from app.api.deps import (
    DbSession, CurrentUser,
    require_participant, require_organizer, get_participant,
)
from app.core.config import settings
from app.models import Marathon, MarathonStatus, GameProposalMode, Game, GameStatus, Challenge, Activity, ActivityType
from app.schemas import GameCreate, GameUpdate, GameResponse, MessageResponse, UserPublic
from app.services.storage import storage_service
from app.services.telegram_notifier import telegram_notifier

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


async def get_game_or_404(db, game_id: int) -> Game:
    result = await db.execute(
        select(Game)
        .options(
            selectinload(Game.proposed_by),
            selectinload(Game.approved_by),
        )
        .where(Game.id == game_id)
    )
    game = result.scalar_one_or_none()
    if not game:
        raise HTTPException(status_code=404, detail="Game not found")
    return game


def game_to_response(game: Game, challenges_count: int = 0) -> GameResponse:
    """Convert Game model to GameResponse schema"""
    return 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,
        status=game.status,
        proposed_by=UserPublic.model_validate(game.proposed_by) if game.proposed_by else None,
        approved_by=UserPublic.model_validate(game.approved_by) if game.approved_by else None,
        challenges_count=challenges_count,
        created_at=game.created_at,
    )


@router.get("/marathons/{marathon_id}/games", response_model=list[GameResponse])
async def list_games(
    marathon_id: int,
    current_user: CurrentUser,
    db: DbSession,
    status_filter: str | None = Query(None, alias="status"),
):
    """List games in marathon. Organizers/admins see all, participants see only approved."""
    # Admins can view without being participant
    participant = await get_participant(db, current_user.id, marathon_id)
    if not participant and not current_user.is_admin:
        raise HTTPException(status_code=403, detail="You are not a participant of this marathon")

    query = (
        select(Game, func.count(Challenge.id).label("challenges_count"))
        .outerjoin(Challenge)
        .options(
            selectinload(Game.proposed_by),
            selectinload(Game.approved_by),
        )
        .where(Game.marathon_id == marathon_id)
        .group_by(Game.id)
        .order_by(Game.created_at.desc())
    )

    # Filter by status if provided
    is_organizer = current_user.is_admin or (participant and participant.is_organizer)
    if status_filter:
        query = query.where(Game.status == status_filter)
    elif not is_organizer:
        # Regular participants only see approved games + their own pending games
        query = query.where(
            (Game.status == GameStatus.APPROVED.value) |
            (Game.proposed_by_id == current_user.id)
        )

    result = await db.execute(query)

    return [game_to_response(row[0], row[1]) for row in result.all()]


@router.get("/marathons/{marathon_id}/games/pending", response_model=list[GameResponse])
async def list_pending_games(marathon_id: int, current_user: CurrentUser, db: DbSession):
    """List pending games for moderation. Organizers only."""
    await require_organizer(db, current_user, marathon_id)

    result = await db.execute(
        select(Game, func.count(Challenge.id).label("challenges_count"))
        .outerjoin(Challenge)
        .options(
            selectinload(Game.proposed_by),
            selectinload(Game.approved_by),
        )
        .where(
            Game.marathon_id == marathon_id,
            Game.status == GameStatus.PENDING.value,
        )
        .group_by(Game.id)
        .order_by(Game.created_at.desc())
    )

    return [game_to_response(row[0], row[1]) for row in result.all()]


@router.post("/marathons/{marathon_id}/games", response_model=GameResponse)
async def add_game(
    marathon_id: int,
    data: GameCreate,
    current_user: CurrentUser,
    db: DbSession,
):
    """Propose a new game. Organizers can auto-approve."""
    # Check marathon exists and is preparing
    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.PREPARING.value:
        raise HTTPException(status_code=400, detail="Cannot add games to active or finished marathon")

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

    # Check if user can propose games based on marathon settings
    is_organizer = participant.is_organizer or current_user.is_admin
    if marathon.game_proposal_mode == GameProposalMode.ORGANIZER_ONLY.value and not is_organizer:
        raise HTTPException(status_code=403, detail="Only organizers can add games to this marathon")

    # Organizers can auto-approve their games
    game_status = GameStatus.APPROVED.value if is_organizer else GameStatus.PENDING.value

    game = Game(
        marathon_id=marathon_id,
        title=data.title,
        download_url=data.download_url,
        genre=data.genre,
        proposed_by_id=current_user.id,
        status=game_status,
        approved_by_id=current_user.id if is_organizer else None,
    )
    db.add(game)

    # Log activity
    activity = Activity(
        marathon_id=marathon_id,
        user_id=current_user.id,
        type=ActivityType.ADD_GAME.value,
        data={"title": game.title, "status": game_status},
    )
    db.add(activity)

    await db.commit()
    await db.refresh(game)

    return GameResponse(
        id=game.id,
        title=game.title,
        cover_url=None,
        download_url=game.download_url,
        genre=game.genre,
        status=game.status,
        proposed_by=UserPublic.model_validate(current_user),
        approved_by=UserPublic.model_validate(current_user) if is_organizer else None,
        challenges_count=0,
        created_at=game.created_at,
    )


@router.get("/games/{game_id}", response_model=GameResponse)
async def get_game(game_id: int, current_user: CurrentUser, db: DbSession):
    game = await get_game_or_404(db, game_id)
    participant = await get_participant(db, current_user.id, game.marathon_id)

    # Check access: organizers see all, participants see approved + own
    if not current_user.is_admin:
        if not participant:
            raise HTTPException(status_code=403, detail="You are not a participant of this marathon")
        if not participant.is_organizer:
            if game.status != GameStatus.APPROVED.value and game.proposed_by_id != current_user.id:
                raise HTTPException(status_code=403, detail="Game not found")

    challenges_count = await db.scalar(
        select(func.count()).select_from(Challenge).where(Challenge.game_id == game_id)
    )

    return game_to_response(game, challenges_count)


@router.patch("/games/{game_id}", response_model=GameResponse)
async def update_game(
    game_id: int,
    data: GameUpdate,
    current_user: CurrentUser,
    db: DbSession,
):
    game = await get_game_or_404(db, game_id)

    # Check if marathon is in preparing state
    result = await db.execute(select(Marathon).where(Marathon.id == game.marathon_id))
    marathon = result.scalar_one()
    if marathon.status != MarathonStatus.PREPARING.value:
        raise HTTPException(status_code=400, detail="Cannot update games in active or finished marathon")

    participant = await get_participant(db, current_user.id, game.marathon_id)

    # Only the one who proposed, organizers, or admin can update
    can_update = (
        current_user.is_admin or
        (participant and participant.is_organizer) or
        game.proposed_by_id == current_user.id
    )
    if not can_update:
        raise HTTPException(status_code=403, detail="Only the one who proposed the game or organizer can update it")

    if data.title is not None:
        game.title = data.title
    if data.download_url is not None:
        game.download_url = data.download_url
    if data.genre is not None:
        game.genre = data.genre

    await db.commit()

    return await get_game(game_id, current_user, db)


@router.delete("/games/{game_id}", response_model=MessageResponse)
async def delete_game(game_id: int, current_user: CurrentUser, db: DbSession):
    game = await get_game_or_404(db, game_id)

    # Check if marathon is in preparing state
    result = await db.execute(select(Marathon).where(Marathon.id == game.marathon_id))
    marathon = result.scalar_one()
    if marathon.status != MarathonStatus.PREPARING.value:
        raise HTTPException(status_code=400, detail="Cannot delete games from active or finished marathon")

    participant = await get_participant(db, current_user.id, game.marathon_id)

    # Only the one who proposed, organizers, or admin can delete
    can_delete = (
        current_user.is_admin or
        (participant and participant.is_organizer) or
        game.proposed_by_id == current_user.id
    )
    if not can_delete:
        raise HTTPException(status_code=403, detail="Only the one who proposed the game or organizer can delete it")

    await db.delete(game)
    await db.commit()

    return MessageResponse(message="Game deleted")


@router.post("/games/{game_id}/approve", response_model=GameResponse)
async def approve_game(game_id: int, current_user: CurrentUser, db: DbSession):
    """Approve a pending game. Organizers only."""
    game = await get_game_or_404(db, game_id)

    await require_organizer(db, current_user, game.marathon_id)

    if game.status != GameStatus.PENDING.value:
        raise HTTPException(status_code=400, detail="Game is not pending")

    # Get marathon title for notification
    marathon_result = await db.execute(select(Marathon).where(Marathon.id == game.marathon_id))
    marathon = marathon_result.scalar_one()

    # Save proposer id before status change
    proposer_id = game.proposed_by_id

    game.status = GameStatus.APPROVED.value
    game.approved_by_id = current_user.id

    # Log activity
    activity = Activity(
        marathon_id=game.marathon_id,
        user_id=current_user.id,
        type=ActivityType.APPROVE_GAME.value,
        data={"title": game.title},
    )
    db.add(activity)

    await db.commit()
    await db.refresh(game)

    # Notify proposer (if not self-approving)
    if proposer_id and proposer_id != current_user.id:
        await telegram_notifier.notify_game_approved(
            db, proposer_id, marathon.title, game.title
        )

    # Need to reload relationships
    game = await get_game_or_404(db, game_id)
    challenges_count = await db.scalar(
        select(func.count()).select_from(Challenge).where(Challenge.game_id == game_id)
    )

    return game_to_response(game, challenges_count)


@router.post("/games/{game_id}/reject", response_model=GameResponse)
async def reject_game(game_id: int, current_user: CurrentUser, db: DbSession):
    """Reject a pending game. Organizers only."""
    game = await get_game_or_404(db, game_id)

    await require_organizer(db, current_user, game.marathon_id)

    if game.status != GameStatus.PENDING.value:
        raise HTTPException(status_code=400, detail="Game is not pending")

    # Get marathon title for notification
    marathon_result = await db.execute(select(Marathon).where(Marathon.id == game.marathon_id))
    marathon = marathon_result.scalar_one()

    # Save proposer id and game title before changes
    proposer_id = game.proposed_by_id
    game_title = game.title

    game.status = GameStatus.REJECTED.value

    # Log activity
    activity = Activity(
        marathon_id=game.marathon_id,
        user_id=current_user.id,
        type=ActivityType.REJECT_GAME.value,
        data={"title": game.title},
    )
    db.add(activity)

    await db.commit()
    await db.refresh(game)

    # Notify proposer
    if proposer_id and proposer_id != current_user.id:
        await telegram_notifier.notify_game_rejected(
            db, proposer_id, marathon.title, game_title
        )

    # Need to reload relationships
    game = await get_game_or_404(db, game_id)
    challenges_count = await db.scalar(
        select(func.count()).select_from(Challenge).where(Challenge.game_id == game_id)
    )

    return game_to_response(game, challenges_count)


@router.post("/games/{game_id}/cover", response_model=GameResponse)
async def upload_cover(
    game_id: int,
    current_user: CurrentUser,
    db: DbSession,
    file: UploadFile = File(...),
):
    game = await get_game_or_404(db, game_id)
    await require_participant(db, current_user.id, game.marathon_id)

    # Validate file
    if not file.content_type.startswith("image/"):
        raise HTTPException(status_code=400, detail="File must be an image")

    contents = await 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 = file.filename.split(".")[-1].lower() if file.filename else "jpg"
    if ext not in settings.ALLOWED_IMAGE_EXTENSIONS:
        raise HTTPException(
            status_code=400,
            detail=f"Invalid file type. Allowed: {settings.ALLOWED_IMAGE_EXTENSIONS}",
        )

    # Delete old cover if exists
    if game.cover_path:
        await storage_service.delete_file(game.cover_path)

    # Upload file
    filename = storage_service.generate_filename(game_id, file.filename)
    file_path = await storage_service.upload_file(
        content=contents,
        folder="covers",
        filename=filename,
        content_type=file.content_type or "image/jpeg",
    )

    game.cover_path = file_path
    await db.commit()

    return await get_game(game_id, current_user, db)
