"""WebSocket game server for real-time multiplayer gameplay."""
import asyncio
import json
from typing import Dict, Set, Optional
from datetime import datetime
from fastapi import WebSocket, WebSocketDisconnect

# Protocol message types
GAME_EVENT_JOIN = 1000
GAME_EVENT_LEAVE = 1001
GAME_EVENT_GAME_CLOSED = 1002
GAME_EVENT_GAME_HOST_CHANGED = 1003
GAME_EVENT_KICKED = 1004
GAME_EVENT_GAME_STATE_CHANGED = 1005
GAME_EVENT_PLAYER_PROPERTIES_CHANGED = 1007
GAME_EVENT_GAME_SAY = 1009
GAME_EVENT_CREATE_ARROW = 2000
GAME_EVENT_DELETE_ARROW = 2001
GAME_EVENT_CREATE_COUNTER = 2002
GAME_EVENT_SET_COUNTER = 2003
GAME_EVENT_DEL_COUNTER = 2004
GAME_EVENT_DRAW_CARDS = 2005
GAME_EVENT_REVEAL_CARDS = 2006
GAME_EVENT_SHUFFLE = 2007
GAME_EVENT_ROLL_DIE = 2008
GAME_EVENT_MOVE_CARD = 2009
GAME_EVENT_FLIP_CARD = 2010
GAME_EVENT_DESTROY_CARD = 2011
GAME_EVENT_ATTACH_CARD = 2012
GAME_EVENT_CREATE_TOKEN = 2013
GAME_EVENT_SET_CARD_ATTR = 2014
GAME_EVENT_SET_CARD_COUNTER = 2015
GAME_EVENT_SET_ACTIVE_PLAYER = 2016
GAME_EVENT_SET_ACTIVE_PHASE = 2017
GAME_EVENT_DUMP_ZONE = 2018
GAME_EVENT_CHANGE_ZONE_PROPERTIES = 2020
GAME_EVENT_REVERSE_TURN = 2021
GAME_EVENT_GAME_LOG_NOTICE = 2022

# Game command types
GAME_COMMAND_KICK_FROM_GAME = 1000
GAME_COMMAND_LEAVE_GAME = 1001
GAME_COMMAND_GAME_SAY = 1002
GAME_COMMAND_SHUFFLE = 1003
GAME_COMMAND_MULLIGAN = 1004
GAME_COMMAND_ROLL_DIE = 1005
GAME_COMMAND_DRAW_CARDS = 1006
GAME_COMMAND_UNDO_DRAW = 1007
GAME_COMMAND_FLIP_CARD = 1008
GAME_COMMAND_ATTACH_CARD = 1009
GAME_COMMAND_CREATE_TOKEN = 1010
GAME_COMMAND_CREATE_ARROW = 1011
GAME_COMMAND_DELETE_ARROW = 1012
GAME_COMMAND_SET_CARD_ATTR = 1013
GAME_COMMAND_SET_CARD_COUNTER = 1014
GAME_COMMAND_INC_CARD_COUNTER = 1015
GAME_COMMAND_READY_START = 1016
GAME_COMMAND_CONCEDE = 1017
GAME_COMMAND_INC_COUNTER = 1018
GAME_COMMAND_CREATE_COUNTER = 1019
GAME_COMMAND_SET_COUNTER = 1020
GAME_COMMAND_DEL_COUNTER = 1021
GAME_COMMAND_NEXT_TURN = 1022
GAME_COMMAND_SET_ACTIVE_PHASE = 1023
GAME_COMMAND_DUMP_ZONE = 1024
GAME_COMMAND_REVEAL_CARDS = 1026
GAME_COMMAND_MOVE_CARD = 1027
GAME_COMMAND_SET_SIDEBOARD_PLAN = 1028
GAME_COMMAND_DECK_SELECT = 1029
GAME_COMMAND_SET_SIDEBOARD_LOCK = 1030
GAME_COMMAND_CHANGE_ZONE_PROPERTIES = 1031
GAME_COMMAND_UNCONCEDE = 1032
GAME_COMMAND_JUDGE = 1033
GAME_COMMAND_REVERSE_TURN = 1034


class GameRoom:
    """Manages a single game room with multiple connected players."""
    
    def __init__(self, room_id: int, game_id: int):
        self.room_id = room_id
        self.game_id = game_id
        self.players: Dict[int, WebSocket] = {}  # player_id -> websocket
        self.host_id: Optional[int] = None
        self.state: dict = {
            "started": False,
            "password_protected": False,
            "players_ready": set(),
            "turn": 1,
            "active_player": None,
            "phases": [],
            "zones": {},
        }
        self.lock = asyncio.Lock()
    
    async def add_player(self, player_id: int, websocket: WebSocket):
        """Add a player to the game."""
        async with self.lock:
            self.players[player_id] = websocket
            if self.host_id is None:
                self.host_id = player_id
    
    async def remove_player(self, player_id: int):
        """Remove a player from the game."""
        async with self.lock:
            self.players.pop(player_id, None)
            if self.host_id == player_id and len(self.players) > 0:
                self.host_id = next(iter(self.players))
    
    async def broadcast(self, message: dict):
        """Broadcast a message to all players."""
        message_str = json.dumps(message)
        for player_id, websocket in list(self.players.items()):
            try:
                await websocket.send_text(message_str)
            except Exception as e:
                print(f"Error sending to player {player_id}: {e}")
    
    async def send_to_player(self, player_id: int, message: dict):
        """Send a message to a specific player."""
        if player_id in self.players:
            try:
                message_str = json.dumps(message)
                await self.players[player_id].send_text(message_str)
            except Exception as e:
                print(f"Error sending to player {player_id}: {e}")
    
    def get_player_count(self) -> int:
        """Get number of connected players."""
        return len(self.players)
    
    def is_full(self, max_players: int = 4) -> bool:
        """Check if game is full."""
        return self.get_player_count() >= max_players


class GameServer:
    """Manages all active game rooms."""
    
    def __init__(self):
        self.rooms: Dict[int, GameRoom] = {}  # game_id -> GameRoom
        self.game_counter = 0
    
    async def create_game(self, room_id: int, host_id: int) -> int:
        """Create a new game and return game ID."""
        self.game_counter += 1
        game_id = self.game_counter
        room = GameRoom(room_id, game_id)
        self.rooms[game_id] = room
        await room.add_player(host_id, None)  # Host joins but websocket added later
        return game_id
    
    async def get_room(self, game_id: int) -> Optional[GameRoom]:
        """Get a game room by ID."""
        return self.rooms.get(game_id)
    
    async def delete_room(self, game_id: int):
        """Delete a game room."""
        room = self.rooms.pop(game_id, None)
        if room:
            # Close all player connections
            for player_id, websocket in list(room.players.items()):
                try:
                    await websocket.close()
                except Exception:
                    pass
    
    def get_active_games(self) -> list:
        """Get list of active game IDs."""
        return list(self.rooms.keys())


# Global game server instance
game_server = GameServer()


async def game_websocket_endpoint(websocket: WebSocket, game_id: int):
    """WebSocket endpoint for game connections."""
    room = await game_server.get_room(game_id)
    if not room:
        await websocket.close(code=4004, reason="Game not found")
        return
    
    # Wait for player ID from client
    player_id = None
    try:
        data = await websocket.receive_text()
        message = json.loads(data)
        if message.get("type") == "join":
            player_id = message.get("player_id")
        else:
            await websocket.close(code=4001, reason="Invalid join message")
            return
    except Exception as e:
        await websocket.close(code=4000, reason="Invalid message")
        return
    
    if not player_id:
        await websocket.close(code=4001, reason="Player ID required")
        return
    
    # Add player to room
    await room.add_player(player_id, websocket)
    
    # Send join confirmation
    await websocket.send_text(json.dumps({
        "type": "joined",
        "player_id": player_id,
        "game_id": game_id,
        "state": room.state,
    }))
    
    # Broadcast join event to other players
    await room.broadcast({
        "type": "game_event",
        "event": GAME_EVENT_JOIN,
        "player_id": player_id,
    })
    
    # Handle messages
    try:
        while True:
            data = await websocket.receive_text()
            message = json.loads(data)
            
            if message.get("type") == "command":
                # Process game command
                await process_game_command(room, player_id, message)
            elif message.get("type") == "chat":
                # Broadcast chat message
                await room.broadcast({
                    "type": "game_event",
                    "event": GAME_EVENT_GAME_SAY,
                    "player_id": player_id,
                    "message": message.get("message"),
                })
            elif message.get("type") == "ping":
                # Respond to ping
                await room.send_to_player(player_id, {
                    "type": "pong",
                    "timestamp": datetime.now().isoformat(),
                })
    except WebSocketDisconnect:
        # Player disconnected
        await room.remove_player(player_id)
        await room.broadcast({
            "type": "game_event",
            "event": GAME_EVENT_LEAVE,
            "player_id": player_id,
        })
    except Exception as e:
        print(f"Error in game WebSocket: {e}")
        await room.remove_player(player_id)


async def process_game_command(room: GameRoom, player_id: int, command: dict):
    """Process a game command from a player."""
    cmd_type = command.get("type")
    
    # Validate command type
    if cmd_type not in [
        GAME_COMMAND_KICK_FROM_GAME,
        GAME_COMMAND_LEAVE_GAME,
        GAME_COMMAND_GAME_SAY,
        GAME_COMMAND_SHUFFLE,
        GAME_COMMAND_MULLIGAN,
        GAME_COMMAND_ROLL_DIE,
        GAME_COMMAND_DRAW_CARDS,
        GAME_COMMAND_UNDO_DRAW,
        GAME_COMMAND_FLIP_CARD,
        GAME_COMMAND_ATTACH_CARD,
        GAME_COMMAND_CREATE_TOKEN,
        GAME_COMMAND_CREATE_ARROW,
        GAME_COMMAND_DELETE_ARROW,
        GAME_COMMAND_SET_CARD_ATTR,
        GAME_COMMAND_SET_CARD_COUNTER,
        GAME_COMMAND_INC_CARD_COUNTER,
        GAME_COMMAND_READY_START,
        GAME_COMMAND_CONCEDE,
        GAME_COMMAND_INC_COUNTER,
        GAME_COMMAND_CREATE_COUNTER,
        GAME_COMMAND_SET_COUNTER,
        GAME_COMMAND_DEL_COUNTER,
        GAME_COMMAND_NEXT_TURN,
        GAME_COMMAND_SET_ACTIVE_PHASE,
        GAME_COMMAND_DUMP_ZONE,
        GAME_COMMAND_REVEAL_CARDS,
        GAME_COMMAND_MOVE_CARD,
        GAME_COMMAND_SET_SIDEBOARD_PLAN,
        GAME_COMMAND_DECK_SELECT,
        GAME_COMMAND_SET_SIDEBOARD_LOCK,
        GAME_COMMAND_CHANGE_ZONE_PROPERTIES,
        GAME_COMMAND_UNCONCEDE,
        GAME_COMMAND_JUDGE,
        GAME_COMMAND_REVERSE_TURN,
    ]:
        await room.send_to_player(player_id, {
            "type": "error",
            "message": f"Invalid command type: {cmd_type}",
        })
        return
    
    # Broadcast command as game event
    await room.broadcast({
        "type": "game_event",
        "event": cmd_type,
        "player_id": player_id,
        **command.get("data", {}),
    })
