"""
Replay API router for Golf game.

Provides endpoints for:
- Viewing game replays
- Creating and managing share links
- Exporting/importing games
- Spectating live games
"""

import hashlib
import logging
from typing import Optional

from fastapi import APIRouter, HTTPException, Query, Depends, Header, WebSocket, WebSocketDisconnect
from fastapi.responses import JSONResponse
from pydantic import BaseModel

logger = logging.getLogger(__name__)

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

# Service instances (set during app startup)
_replay_service = None
_auth_service = None
_spectator_manager = None
_room_manager = None


def set_replay_service(service) -> None:
    """Set the replay service instance."""
    global _replay_service
    _replay_service = service


def set_auth_service(service) -> None:
    """Set the auth service instance."""
    global _auth_service
    _auth_service = service


def set_spectator_manager(manager) -> None:
    """Set the spectator manager instance."""
    global _spectator_manager
    _spectator_manager = manager


def set_room_manager(manager) -> None:
    """Set the room manager instance."""
    global _room_manager
    _room_manager = manager


# -------------------------------------------------------------------------
# Auth Dependencies
# -------------------------------------------------------------------------

async def get_current_user(authorization: Optional[str] = Header(None)):
    """Get current user from Authorization header."""
    if not authorization or not _auth_service:
        return None

    parts = authorization.split()
    if len(parts) != 2 or parts[0].lower() != "bearer":
        return None

    token = parts[1]
    return await _auth_service.get_user_from_token(token)


async def require_auth(user=Depends(get_current_user)):
    """Require authenticated user."""
    if not user:
        raise HTTPException(status_code=401, detail="Authentication required")
    return user


# -------------------------------------------------------------------------
# Request/Response Models
# -------------------------------------------------------------------------

class ShareLinkRequest(BaseModel):
    """Request to create a share link."""
    title: Optional[str] = None
    description: Optional[str] = None
    expires_days: Optional[int] = None


class ImportGameRequest(BaseModel):
    """Request to import a game."""
    export_data: dict


# -------------------------------------------------------------------------
# Replay Endpoints
# -------------------------------------------------------------------------

@router.get("/game/{game_id}")
async def get_replay(game_id: str, user=Depends(get_current_user)):
    """
    Get full replay for a game.

    Returns all frames with game state at each step.
    Requires authentication and permission to view the game.
    """
    if not _replay_service:
        raise HTTPException(status_code=503, detail="Replay service unavailable")

    # Check permission
    user_id = user.id if user else None
    if not await _replay_service.can_view_game(user_id, game_id):
        raise HTTPException(status_code=403, detail="Cannot view this game")

    try:
        replay = await _replay_service.build_replay(game_id)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    return {
        "game_id": replay.game_id,
        "room_code": replay.room_code,
        "frames": [
            {
                "index": f.event_index,
                "event_type": f.event_type,
                "event_data": f.event_data,
                "timestamp": f.timestamp,
                "state": f.game_state,
                "player_id": f.player_id,
            }
            for f in replay.frames
        ],
        "metadata": {
            "players": replay.player_names,
            "winner": replay.winner,
            "final_scores": replay.final_scores,
            "duration": replay.total_duration_seconds,
            "total_rounds": replay.total_rounds,
            "options": replay.options,
        },
    }


@router.get("/game/{game_id}/frame/{frame_index}")
async def get_replay_frame(game_id: str, frame_index: int, user=Depends(get_current_user)):
    """
    Get a specific frame from a replay.

    Useful for seeking without loading the entire replay.
    """
    if not _replay_service:
        raise HTTPException(status_code=503, detail="Replay service unavailable")

    user_id = user.id if user else None
    if not await _replay_service.can_view_game(user_id, game_id):
        raise HTTPException(status_code=403, detail="Cannot view this game")

    frame = await _replay_service.get_replay_frame(game_id, frame_index)
    if not frame:
        raise HTTPException(status_code=404, detail="Frame not found")

    return {
        "index": frame.event_index,
        "event_type": frame.event_type,
        "event_data": frame.event_data,
        "timestamp": frame.timestamp,
        "state": frame.game_state,
        "player_id": frame.player_id,
    }


# -------------------------------------------------------------------------
# Share Link Endpoints
# -------------------------------------------------------------------------

@router.post("/game/{game_id}/share")
async def create_share_link(
    game_id: str,
    request: ShareLinkRequest,
    user=Depends(require_auth),
):
    """
    Create shareable link for a game.

    Only users who played in the game can create share links.
    """
    if not _replay_service:
        raise HTTPException(status_code=503, detail="Replay service unavailable")

    # Validate expires_days
    if request.expires_days is not None and (request.expires_days < 1 or request.expires_days > 365):
        raise HTTPException(status_code=400, detail="expires_days must be between 1 and 365")

    # Check if user played in the game
    if not await _replay_service.can_view_game(user.id, game_id):
        raise HTTPException(status_code=403, detail="Can only share games you played in")

    try:
        share_code = await _replay_service.create_share_link(
            game_id=game_id,
            user_id=user.id,
            title=request.title,
            description=request.description,
            expires_days=request.expires_days,
        )
    except Exception as e:
        logger.error(f"Failed to create share link: {e}")
        raise HTTPException(status_code=500, detail="Failed to create share link")

    return {
        "share_code": share_code,
        "share_url": f"/replay/{share_code}",
        "expires_days": request.expires_days,
    }


@router.get("/shared/{share_code}")
async def get_shared_replay(share_code: str):
    """
    Get replay via share code (public endpoint).

    No authentication required for public share links.
    """
    if not _replay_service:
        raise HTTPException(status_code=503, detail="Replay service unavailable")

    shared = await _replay_service.get_shared_game(share_code)
    if not shared:
        raise HTTPException(status_code=404, detail="Shared game not found or expired")

    try:
        replay = await _replay_service.build_replay(str(shared["game_id"]))
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    return {
        "title": shared.get("title"),
        "description": shared.get("description"),
        "view_count": shared["view_count"],
        "created_at": shared["created_at"].isoformat() if shared.get("created_at") else None,
        "game_id": str(shared["game_id"]),
        "room_code": replay.room_code,
        "frames": [
            {
                "index": f.event_index,
                "event_type": f.event_type,
                "event_data": f.event_data,
                "timestamp": f.timestamp,
                "state": f.game_state,
                "player_id": f.player_id,
            }
            for f in replay.frames
        ],
        "metadata": {
            "players": replay.player_names,
            "winner": replay.winner,
            "final_scores": replay.final_scores,
            "duration": replay.total_duration_seconds,
            "total_rounds": replay.total_rounds,
            "options": replay.options,
        },
    }


@router.get("/shared/{share_code}/info")
async def get_shared_info(share_code: str):
    """
    Get info about a shared game without full replay data.

    Useful for preview/metadata display.
    """
    if not _replay_service:
        raise HTTPException(status_code=503, detail="Replay service unavailable")

    shared = await _replay_service.get_shared_game(share_code)
    if not shared:
        raise HTTPException(status_code=404, detail="Shared game not found or expired")

    return {
        "title": shared.get("title"),
        "description": shared.get("description"),
        "view_count": shared["view_count"],
        "created_at": shared["created_at"].isoformat() if shared.get("created_at") else None,
        "room_code": shared.get("room_code"),
        "num_players": shared.get("num_players"),
        "num_rounds": shared.get("num_rounds"),
    }


@router.delete("/shared/{share_code}")
async def delete_share_link(share_code: str, user=Depends(require_auth)):
    """Delete a share link (creator only)."""
    if not _replay_service:
        raise HTTPException(status_code=503, detail="Replay service unavailable")

    deleted = await _replay_service.delete_share_link(share_code, user.id)
    if not deleted:
        raise HTTPException(status_code=404, detail="Share link not found or not authorized")

    return {"deleted": True}


@router.get("/my-shares")
async def get_my_shares(user=Depends(require_auth)):
    """Get all share links created by the current user."""
    if not _replay_service:
        raise HTTPException(status_code=503, detail="Replay service unavailable")

    shares = await _replay_service.get_user_shared_games(user.id)
    return {
        "shares": [
            {
                "share_code": s["share_code"],
                "game_id": str(s["game_id"]),
                "title": s.get("title"),
                "view_count": s["view_count"],
                "created_at": s["created_at"].isoformat() if s.get("created_at") else None,
                "expires_at": s["expires_at"].isoformat() if s.get("expires_at") else None,
            }
            for s in shares
        ],
    }


# -------------------------------------------------------------------------
# Export/Import Endpoints
# -------------------------------------------------------------------------

@router.get("/game/{game_id}/export")
async def export_game(game_id: str, user=Depends(require_auth)):
    """
    Export game as downloadable JSON.

    Returns the complete game data suitable for backup or sharing.
    """
    if not _replay_service:
        raise HTTPException(status_code=503, detail="Replay service unavailable")

    if not await _replay_service.can_view_game(user.id, game_id):
        raise HTTPException(status_code=403, detail="Cannot export this game")

    try:
        export_data = await _replay_service.export_game(game_id)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    # Return as downloadable JSON
    return JSONResponse(
        content=export_data,
        headers={
            "Content-Disposition": f'attachment; filename="golf-game-{game_id[:8]}.json"'
        },
    )


@router.post("/import")
async def import_game(request: ImportGameRequest, user=Depends(require_auth)):
    """
    Import a game from JSON export.

    Creates a new game record from the exported data.
    """
    if not _replay_service:
        raise HTTPException(status_code=503, detail="Replay service unavailable")

    try:
        new_game_id = await _replay_service.import_game(request.export_data, user.id)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"Import failed: {e}")
        raise HTTPException(status_code=500, detail="Failed to import game")

    return {
        "game_id": new_game_id,
        "message": "Game imported successfully",
    }


# -------------------------------------------------------------------------
# Game History
# -------------------------------------------------------------------------

@router.get("/history")
async def get_game_history(
    limit: int = Query(default=20, ge=1, le=100),
    offset: int = Query(default=0, ge=0),
    user=Depends(require_auth),
):
    """Get game history for the current user."""
    if not _replay_service:
        raise HTTPException(status_code=503, detail="Replay service unavailable")

    games = await _replay_service.get_user_game_history(user.id, limit, offset)
    return {
        "games": [
            {
                "game_id": str(g["id"]),
                "room_code": g["room_code"],
                "status": g["status"],
                "completed_at": g["completed_at"].isoformat() if g.get("completed_at") else None,
                "num_players": g["num_players"],
                "num_rounds": g["num_rounds"],
                "won": g.get("winner_id") == user.id,
            }
            for g in games
        ],
        "limit": limit,
        "offset": offset,
    }


# -------------------------------------------------------------------------
# Spectator Endpoints
# -------------------------------------------------------------------------

@router.websocket("/spectate/{room_code}")
async def spectate_game(websocket: WebSocket, room_code: str):
    """
    WebSocket endpoint for spectating live games.

    Spectators receive real-time game state updates but cannot interact.
    Supports optional authentication via token query parameter.
    """
    await websocket.accept()

    # Optional authentication for spectators
    token = websocket.query_params.get("token")
    spectator_user = None
    if token and _auth_service:
        try:
            spectator_user = await _auth_service.get_user_from_token(token)
        except Exception:
            pass  # Anonymous spectator

    if not _spectator_manager or not _room_manager:
        await websocket.close(code=4003, reason="Spectator service unavailable")
        return

    # Find the game by room code
    room = _room_manager.get_room(room_code.upper())
    if not room:
        await websocket.close(code=4004, reason="Game not found")
        return

    game_id = room_code.upper()  # Use room code as identifier for spectators

    # Add spectator
    added = await _spectator_manager.add_spectator(game_id, websocket)
    if not added:
        await websocket.close(code=4005, reason="Spectator limit reached")
        return

    try:
        # Send initial game state
        game_state = room.game.get_state(None)  # No player perspective
        await websocket.send_json({
            "type": "spectator_joined",
            "game_state": game_state,
            "spectator_count": _spectator_manager.get_spectator_count(game_id),
            "players": room.player_list(),
            "authenticated": spectator_user is not None,
        })

        # Keep connection alive
        while True:
            data = await websocket.receive_text()
            if data == "ping":
                await websocket.send_text("pong")

    except WebSocketDisconnect:
        pass
    except Exception as e:
        logger.debug(f"Spectator connection error: {e}")
    finally:
        await _spectator_manager.remove_spectator(game_id, websocket)


@router.get("/spectate/{room_code}/count")
async def get_spectator_count(room_code: str):
    """Get the number of spectators for a game."""
    if not _spectator_manager:
        return {"count": 0}

    count = _spectator_manager.get_spectator_count(room_code.upper())
    return {"count": count}


@router.get("/spectate/active")
async def get_active_spectated_games():
    """Get list of games with active spectators."""
    if not _spectator_manager:
        return {"games": []}

    games = _spectator_manager.get_games_with_spectators()
    return {
        "games": [
            {"room_code": game_id, "spectator_count": count}
            for game_id, count in games.items()
        ],
    }
