from typing import List, Optional from fastapi import APIRouter, HTTPException, Query, status from app.api.websocket import manager from app.game import game_engine from app.models import ( ApiResponse, AvailableMovesResponse, BoardState, MoveCheckResult, MoveRequest, MoveResponse, Player, PlayerCreate, TurnInfo, ) router = APIRouter() @router.get("/health", response_model=ApiResponse, tags=["System"]) async def health_check(): return ApiResponse( success=True, message="botWebWars backend service is healthy", data={"service": "FastAPI", "status": "running"}, ) @router.post( "/players", response_model=Player, status_code=status.HTTP_201_CREATED, summary="Register a new player on the board", tags=["Players"], ) @router.post( "/register", response_model=Player, status_code=status.HTTP_201_CREATED, include_in_schema=False, ) async def register_player(player_in: PlayerCreate): player = await game_engine.register_player(player_in) board_state = await game_engine.get_board_state() # Broadcast to all connected WebSocket clients await manager.broadcast({ "event": "player_joined", "player": player.model_dump(), "player_count": board_state.player_count, "turn": board_state.turn.model_dump(), }) return player @router.get( "/players", response_model=List[Player], summary="List all registered players", tags=["Players"], ) async def list_players(): return await game_engine.get_all_players() @router.get( "/players/{player_id}", response_model=Player, summary="Get details of a specific player", tags=["Players"], ) async def get_player(player_id: str): player = await game_engine.get_player(player_id) if not player: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Player '{player_id}' not found", ) return player @router.delete( "/players/{player_id}", response_model=ApiResponse, summary="Remove a player from the board", tags=["Players"], ) async def remove_player(player_id: str): removed = await game_engine.remove_player(player_id) if not removed: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Player '{player_id}' not found", ) board_state = await game_engine.get_board_state() await manager.broadcast({ "event": "player_left", "player_id": player_id, "player_count": board_state.player_count, "turn": board_state.turn.model_dump(), }) return ApiResponse( success=True, message=f"Player '{player_id}' successfully removed", ) # ========================================== # Movement & Turn Endpoints # ========================================== @router.get( "/players/{player_id}/available-moves", response_model=AvailableMovesResponse, summary="Check availability of all 8 movement directions (cardinal & diagonal)", tags=["Movement"], ) async def get_available_moves(player_id: str): try: return await game_engine.get_available_moves(player_id) except KeyError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Player '{player_id}' not found", ) @router.get( "/players/{player_id}/check-move", response_model=MoveCheckResult, summary="Check if a specific direction is available for movement", tags=["Movement"], ) async def check_single_move( player_id: str, direction: str = Query( ..., description="Direction to check: UP, DOWN, LEFT, RIGHT, UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT (or N, S, E, W, NW, NE, SW, SE)", ), ): try: return await game_engine.check_single_move(player_id, direction) except KeyError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Player '{player_id}' not found", ) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) @router.post( "/players/{player_id}/move", response_model=MoveResponse, summary="Move player in a cardinal or diagonal direction during their turn", tags=["Movement"], ) async def move_player(player_id: str, move_req: MoveRequest): try: dx, dy, dir_name = move_req.get_delta() result = await game_engine.move_player(player_id, dx, dy, dir_name) # Broadcast move event to all connected WebSocket clients await manager.broadcast({ "event": "player_moved", "player": result.player.model_dump(), "direction": result.direction, "previous_position": result.previous_position, "new_position": result.new_position, "turn": result.turn.model_dump(), }) return result except KeyError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Player '{player_id}' not found", ) except PermissionError as e: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e), ) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) @router.post( "/players/{player_id}/pass", response_model=TurnInfo, summary="Pass the current turn to the next player", tags=["Movement"], ) async def pass_turn(player_id: str): try: next_turn = await game_engine.pass_turn(player_id) await manager.broadcast({ "event": "turn_passed", "passed_by": player_id, "turn": next_turn.model_dump(), }) return next_turn except KeyError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Player '{player_id}' not found", ) except PermissionError as e: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e), ) @router.get( "/turn", response_model=TurnInfo, summary="Get current turn details and round information", tags=["Turn"], ) async def get_turn(): board = await game_engine.get_board_state() return board.turn # ========================================== # Board Endpoints # ========================================== @router.get( "/board", response_model=BoardState, summary="Get full board configuration and player positions", tags=["Board"], ) async def get_board(): return await game_engine.get_board_state() @router.post( "/board/reset", response_model=ApiResponse, summary="Reset the board and clear all players", tags=["Board"], ) async def reset_board(): await game_engine.reset() board = await game_engine.get_board_state() await manager.broadcast({ "event": "board_reset", "player_count": 0, "turn": board.turn.model_dump(), }) return ApiResponse( success=True, message="Board reset successfully. All players cleared.", )