from __future__ import annotations

import logging
import os
from typing import Any

from .bot import Bot
from .enums import Action, CellType
from .helpers import new_game_helpers
from .models import Bomb, Field, GameState, Player, Position
from .network import connect_ws

_MSG_WELCOME = "welcome"
_MSG_BACK_TO_LOBBY = "back_to_lobby"
_MSG_UPDATE_LOBBY = "update_lobby"
_MSG_PLAYER_STATUS_UPDATE = "player_status_update"
_MSG_SERVER_ERROR = "error"
_MSG_CLASSIC_INPUT = "classic_input"
_MSG_CLASSIC_STATE = "classic_state"
_MSG_GAME_START = "game_start"

_logger = logging.getLogger(__name__)


def run(user_bot: Bot) -> None:
    ws_url = os.getenv("BOMBAHEAD_WS_URL", "ws://localhost:8038/ws")

    token = os.getenv("BOMBAHEAD_TOKEN")
    if not token:
        token = os.getenv("BOMBERMAN_CLIENT_AUTH_TOKEN")
    if not token:
        token = "dev-token-local"

    _logger.info("Connecting to %s...", ws_url)

    client = connect_ws(ws_url, token)
    try:
        client.send(_MSG_PLAYER_STATUS_UPDATE, {"isReady": True, "authToken": token})

        my_id = ""
        while True:
            msg = client.read_message()

            if msg.type == _MSG_WELCOME:
                payload = _as_dict(msg.payload)
                my_id = str(payload.get("clientId", ""))
                _logger.info("Connected as %s", my_id)

            elif msg.type == _MSG_UPDATE_LOBBY:
                continue

            elif msg.type == _MSG_SERVER_ERROR:
                payload = _as_dict(msg.payload)
                message = payload.get("message")
                _logger.info("Server error: %s", message)

            elif msg.type == _MSG_GAME_START:
                _logger.info("Game started")

            elif msg.type == _MSG_BACK_TO_LOBBY:
                client.send(_MSG_PLAYER_STATUS_UPDATE, {"isReady": True})

            elif msg.type == _MSG_CLASSIC_STATE:
                state = parse_classic_state(msg.payload, my_id)
                helpers = new_game_helpers(state)
                action = user_bot.get_next_move(state, helpers)
                client.send(_MSG_CLASSIC_INPUT, {"move": action.value})

            else:
                _logger.info("Ignoring message type %r", msg.type)

    finally:
        client.close()


def parse_classic_state(payload: Any, my_id: str) -> GameState:
    data = _as_dict(payload)

    players_raw = data.get("players", [])
    field_raw = _as_dict(data.get("field", {}))
    bombs_raw = data.get("bombs", [])
    explosions_raw = data.get("explosions", [])

    width = int(field_raw.get("width", 0))
    height = int(field_raw.get("height", 0))
    field_values = field_raw.get("field", [])

    cells: list[CellType] = [CellType.AIR] * max(0, width * height)
    for idx in range(min(len(cells), len(field_values))):
        cells[idx] = decode_cell(field_values[idx])

    players = [_parse_player(p) for p in players_raw if isinstance(p, dict)]

    state = GameState(
        players=players,
        field=Field(width=width, height=height, cells=cells),
        bombs=[_parse_bomb(b) for b in bombs_raw if isinstance(b, dict)],
        explosions=[_parse_position(e) for e in explosions_raw if isinstance(e, dict)],
    )

    for player in players:
        if player.id == my_id:
            state.me = player
        else:
            state.opponents.append(player)

    if state.me is None and players:
        state.me = players[0]
        if len(players) > 1:
            state.opponents = players[1:]

    return state


def decode_cell(raw: Any) -> CellType:
    if isinstance(raw, str):
        try:
            return CellType(raw)
        except ValueError:
            return CellType.AIR

    if isinstance(raw, bool):
        raise ValueError(f"unsupported cell encoding: {raw!r}")

    if isinstance(raw, int):
        if raw == 0:
            return CellType.WALL
        if raw == 1:
            return CellType.AIR
        if raw == 2:
            return CellType.BOX
        return CellType.AIR

    raise ValueError(f"unsupported cell encoding: {raw!r}")


def _as_dict(value: Any) -> dict[str, Any]:
    if isinstance(value, dict):
        return value
    raise ValueError("expected payload object")


def _parse_position(data: dict[str, Any]) -> Position:
    return Position(x=int(data.get("x", 0)), y=int(data.get("y", 0)))


def _parse_player(data: dict[str, Any]) -> Player:
    return Player(
        id=str(data.get("id", "")),
        pos=_parse_position(_as_dict(data.get("pos", {}))),
        health=int(data.get("health", 0)),
        score=int(data.get("score", 0)),
    )


def _parse_bomb(data: dict[str, Any]) -> Bomb:
    return Bomb(pos=_parse_position(_as_dict(data.get("pos", {}))), fuse=int(data.get("fuse", 0)))
