"""
房间管理器 —— 内存中管理所有游戏房间
所有距离/坐标单位为像素（世界 4000×3000）
"""
from __future__ import annotations

import asyncio
import math
import time
import uuid
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional

from fastapi import WebSocket

from app.game.heroes import DEFAULT_HERO_ID, get_hero

# ─── 世界尺寸常量 ─────────────────────────────────────────
WORLD_W = 4000
WORLD_H = 3000

class RoomStatus(str, Enum):
    WAITING  = "waiting"
    PLAYING  = "playing"
    FINISHED = "finished"


class Team(str, Enum):
    RED  = "red"
    BLUE = "blue"


# ═══════════════════════════════════════════════════════════
# 游戏实体
# ═══════════════════════════════════════════════════════════

@dataclass
class Mirror:
    """水镜实体"""
    id: str
    owner_id: str
    team: Team
    x: float               # 像素坐标
    y: float
    dir_x: float           # 镜面长轴方向单位向量
    dir_y: float
    created_at: float
    duration: float = 14.0
    active: bool = True
    used_e: bool = False

    @property
    def expired(self) -> bool:
        return time.time() - self.created_at >= self.duration


@dataclass
class Projectile:
    """投射物"""
    id: str
    owner_id: str
    team: Team
    x: float               # 像素坐标
    y: float
    dx: float              # 方向单位向量
    dy: float
    speed: float           # 像素/tick
    damage: float
    max_dist: float        # 最大飞行距离（像素）
    traveled: float = 0.0
    hit: bool = False
    shard_index: int = 0   # 碎片索引：0=中间, -1=左, 1=右
    proj_type: str = "shard"  # "shard" | "energy_ball"
    hit_radius: float = 80.0  # 碰撞半径（像素）
    homing: bool = False       # 是否追踪最近敌人
    bonus_magic_dmg: float = 0.0  # 命中附加法术伤害（已算好绝对值）
    root_duration: float = 0.0    # 命中后定身时长（秒）
    damage_mult: float = 1.0      # 伤害倍率（R 小球 0.25）

    def to_dict(self) -> dict:
        return {
            "id": self.id, "owner_id": self.owner_id, "team": self.team.value,
            "x": round(self.x, 1), "y": round(self.y, 1), "hit": self.hit,
        }


@dataclass
class DamageNumber:
    """飘字伤害数字"""
    target_id: str
    amount: int
    x: float               # 像素坐标
    y: float
    tick: int = 0


@dataclass
class VisualEffect:
    """视觉特效事件"""
    type: str
    x: float               # 像素坐标
    y: float
    data: dict = field(default_factory=dict)
    tick: int = 0


# ═══════════════════════════════════════════════════════════
# 玩家
# ═══════════════════════════════════════════════════════════

@dataclass
class Player:
    user_id: str
    nickname: str
    team: Team
    ws: Optional[WebSocket] = None

    hero_id: str = DEFAULT_HERO_ID

    # 像素坐标
    x: float = 0.0
    y: float = 0.0
    alive: bool = True
    hp: int = 0
    max_hp: int = 0

    # 属性（从英雄定义拷贝，像素单位）
    physical_attack: int = 0
    magical_attack: int = 0
    physical_defense: int = 0
    magical_defense: int = 0
    speed: float = 12.0
    attack_range: float = 160.0
    attack_angle: float = 1.047
    attack_cd: float = 0.8

    facing: float = 0.0
    attack_cd_remaining: float = 0.0
    skill_cds: dict = field(default_factory=dict)
    move_lock_until: float = 0.0
    mirror_id: Optional[str] = None

    # 缀魂者技能 buff 到期时间戳
    sw_f_until: float = 0.0  # F 强化（射程+追踪+附伤）
    sw_e_until: float = 0.0  # E 强化（定身，消耗型）
    sw_r_until: float = 0.0  # R 强化（额外弹丸，消耗型）
    # 定身状态
    root_until: float = 0.0  # 被定身到此时间

    def init_from_hero(self):
        hero = get_hero(self.hero_id)
        if not hero:
            return
        s = hero.stats
        self.max_hp = s.hp
        self.hp = s.hp
        self.physical_attack = s.physical_attack
        self.magical_attack = s.magical_attack
        self.physical_defense = s.physical_defense
        self.magical_defense = s.magical_defense
        self.speed = s.speed
        self.attack_range = s.attack_range
        self.attack_angle = s.attack_angle
        self.attack_cd = s.attack_cd
        self.attack_cd_remaining = 0.0
        self.skill_cds = {sk.key: 0.0 for sk in hero.skills}
        self.alive = True
        self.mirror_id = None


# ═══════════════════════════════════════════════════════════
# 据点 / 房间
# ═══════════════════════════════════════════════════════════

@dataclass
class CapturePoint:
    id: int
    x: float               # 像素坐标
    y: float
    owner: Optional[Team] = None
    progress: float = 0.0
    radius: float = 240.0  # 像素


@dataclass
class Room:
    room_id: str
    name: str
    host_id: str
    status: RoomStatus = RoomStatus.WAITING
    max_players: int = 6
    players: dict[str, Player] = field(default_factory=dict)
    created_at: float = field(default_factory=time.time)

    capture_points: list[CapturePoint] = field(default_factory=list)
    mirrors: dict[str, Mirror] = field(default_factory=dict)
    projectiles: list[Projectile] = field(default_factory=list)
    damage_numbers: list[DamageNumber] = field(default_factory=list)
    effects: list[VisualEffect] = field(default_factory=list)
    pending_swaps: list = field(default_factory=list)
    delayed_balls: list = field(default_factory=list)   # R弹幕延迟小球
    game_task: Optional[asyncio.Task] = None

    def player_count(self) -> int:
        return len(self.players)

    def red_count(self) -> int:
        return sum(1 for p in self.players.values() if p.team == Team.RED)

    def blue_count(self) -> int:
        return sum(1 for p in self.players.values() if p.team == Team.BLUE)

    def to_dict(self) -> dict:
        return {
            "room_id": self.room_id,
            "name": self.name,
            "host_id": self.host_id,
            "status": self.status.value,
            "max_players": self.max_players,
            "player_count": self.player_count(),
            "players": [
                {
                    "user_id": p.user_id,
                    "nickname": p.nickname,
                    "team": p.team.value,
                    "hero_id": p.hero_id,
                }
                for p in self.players.values()
            ],
        }


# ═══════════════════════════════════════════════════════════
# 房间管理器
# ═══════════════════════════════════════════════════════════

class RoomManager:
    def __init__(self):
        self.rooms: dict[str, Room] = {}

    def create_room(self, name: str, host_id: str, host_nickname: str) -> Room:
        room_id = str(uuid.uuid4())[:8]
        room = Room(room_id=room_id, name=name, host_id=host_id)
        room.players[host_id] = Player(
            user_id=host_id, nickname=host_nickname, team=Team.RED
        )
        self.rooms[room_id] = room
        return room

    def get_room(self, room_id: str) -> Optional[Room]:
        return self.rooms.get(room_id)

    def list_rooms(self) -> list[dict]:
        return [
            room.to_dict()
            for room in self.rooms.values()
            if room.status != RoomStatus.FINISHED
        ]

    def join_room(self, room_id: str, user_id: str, nickname: str) -> Optional[Room]:
        room = self.rooms.get(room_id)
        if not room or room.status != RoomStatus.WAITING:
            return None
        if room.player_count() >= room.max_players:
            return None
        if user_id in room.players:
            return room
        team = Team.RED if room.red_count() <= room.blue_count() else Team.BLUE
        room.players[user_id] = Player(
            user_id=user_id, nickname=nickname, team=team
        )
        return room

    def leave_room(self, room_id: str, user_id: str) -> bool:
        room = self.rooms.get(room_id)
        if not room or user_id not in room.players:
            return False
        del room.players[user_id]
        if room.player_count() == 0:
            if room.game_task:
                room.game_task.cancel()
            del self.rooms[room_id]
            return True
        if room.host_id == user_id:
            room.host_id = next(iter(room.players))
        return True

    def delete_room(self, room_id: str):
        room = self.rooms.pop(room_id, None)
        if room and room.game_task:
            room.game_task.cancel()

    def select_hero(self, room_id: str, user_id: str, hero_id: str) -> bool:
        room = self.rooms.get(room_id)
        if not room or room.status != RoomStatus.WAITING:
            return False
        player = room.players.get(user_id)
        if not player:
            return False
        hero = get_hero(hero_id)
        if not hero:
            return False
        player.hero_id = hero_id
        return True


room_manager = RoomManager()
