"""
游戏 WebSocket —— 房间内实时通信
"""
import asyncio
import json
import logging
import math

from fastapi import APIRouter, WebSocket, WebSocketDisconnect

from app.auth import decode_access_token
from app.game import (
    room_manager, init_game_state, update_player_position,
    broadcast, game_loop, get_game_state,
    process_attack, process_skill, list_heroes,
)
from app.game.logic import update_player_facing
from app.game.room_manager import RoomStatus

logger = logging.getLogger(__name__)
router = APIRouter()


@router.websocket("/ws/game/{room_id}")
async def game_ws(ws: WebSocket, room_id: str):
    """
    游戏房间 WebSocket
    连接后第一条消息发送: {"type":"auth","token":"xxx"}
    之后:
      {"type":"move","dx":1,"dy":0}
      {"type":"facing","angle":1.57}
      {"type":"start"}
      {"type":"select_hero","hero_id":"red_lady"}
      {"type":"attack"}
      {"type":"skill","key":"f","target_x":2000,"target_y":1500,"angle":0}
    """
    user_id = None
    room = None

    try:
        await ws.accept()

        # 认证
        auth_msg = await ws.receive_text()
        auth_data = json.loads(auth_msg)
        if auth_data.get("type") != "auth":
            await ws.close(code=4001, reason="首条消息需为认证信息")
            return

        payload = decode_access_token(auth_data.get("token", ""))
        if not payload:
            await ws.close(code=4002, reason="Token 无效")
            return

        user_id = payload["sub"]
        room = room_manager.get_room(room_id)

        if not room or user_id not in room.players:
            await ws.close(code=4003, reason="不在该房间中")
            return

        # 绑定 WebSocket
        room.players[user_id].ws = ws

        # 发送房间当前状态 + 英雄列表
        await ws.send_text(json.dumps({
            "type": "room_info",
            "data": room.to_dict(),
            "heroes": list_heroes(),
        }))

        # 通知其他人
        await broadcast(room, {
            "type": "player_joined",
            "data": room.to_dict(),
        })

        # 消息循环
        while True:
            raw = await ws.receive_text()
            data = json.loads(raw)
            msg_type = data.get("type")

            if msg_type == "start" and user_id == room.host_id:
                if room.status == RoomStatus.WAITING and room.player_count() >= 2:
                    init_game_state(room)
                    await broadcast(room, {"type": "game_start"})
                    room.game_task = asyncio.create_task(game_loop(room))

            elif msg_type == "select_hero" and room.status == RoomStatus.WAITING:
                hero_id = data.get("hero_id", "")
                ok = room_manager.select_hero(room_id, user_id, hero_id)
                if ok:
                    await broadcast(room, {
                        "type": "hero_selected",
                        "data": room.to_dict(),
                    })

            elif msg_type == "move" and room.status == RoomStatus.PLAYING:
                dx = max(-1, min(1, data.get("dx", 0)))
                dy = max(-1, min(1, data.get("dy", 0)))
                update_player_position(room, user_id, dx, dy)

            elif msg_type == "facing" and room.status == RoomStatus.PLAYING:
                angle = float(data.get("angle", 0))
                update_player_facing(room, user_id, angle)

            elif msg_type == "attack" and room.status == RoomStatus.PLAYING:
                atx = float(data.get("target_x", 0))
                aty = float(data.get("target_y", 0))
                process_attack(room, user_id, atx, aty)

            elif msg_type == "skill" and room.status == RoomStatus.PLAYING:
                key = data.get("key", "")
                if key in ("f", "e", "r"):
                    tx = float(data.get("target_x", 0))
                    ty = float(data.get("target_y", 0))
                    angle = float(data.get("angle", 0))
                    process_skill(room, user_id, key, tx, ty, angle)

    except WebSocketDisconnect:
        pass
    except Exception as e:
        logger.error(f"Game WS error: {e}", exc_info=True)
    finally:
        if room and user_id and user_id in room.players:
            room.players[user_id].ws = None
            if room.status == RoomStatus.WAITING:
                room_manager.leave_room(room_id, user_id)
                await broadcast(room, {
                    "type": "player_left",
                    "data": room.to_dict() if room.players else {},
                })
