from __future__ import annotations

import shlex
from typing import Dict, List, Optional

import typer
from rich.console import Console
from rich.table import Table

from .data import Resource
from .game import Game, GameConfig, Phase
from .sdk import CatanEnv, ActionType
from .ml.agents import Agent, RandomAgent

app = typer.Typer(help="Settlers of Catan interactive CLI.")


def parse_resource(token: str) -> Resource:
    return Resource[token.strip().upper()]


class CLIController:
    def __init__(self, game: Game, bots: Optional[Dict[str, Agent]] = None):
        self.game = game
        self.console = Console()
        self.env = CatanEnv(game.config)
        self.env.game = game
        self.bot_agents = bots or {}

    def run(self) -> None:
        self.console.print(
            "[bold green]Добро пожаловать в CLI Catan.[/bold green] Введите 'help' для списка команд."
        )
        while True:
            self._resolve_bot_discards()
            if self.game.winner:
                self.console.print(
                    f"[bold yellow]{self.game.winner} выиграл партию![/bold yellow]"
                )
                break
            if self.game.pending_discards:
                # Human players must resolve their discards before continuing.
                pass
            current_name = self.game.current_player.name
            if current_name in self.bot_agents and not self.game.pending_discards:
                self._run_bot_turn(current_name)
                continue
            prompt = (
                f"[{self.game.phase.value}] {self.game.current_player.name}> "
            )
            try:
                raw = input(prompt)
            except (EOFError, KeyboardInterrupt):
                self.console.print("\nВыход из игры.")
                break
            command = raw.strip()
            if not command:
                continue
            if command.lower() in {"quit", "exit"}:
                break
            try:
                self.handle_command(command)
            except Exception as exc:  # noqa: BLE001
                self.console.print(f"[red]{exc}[/red]")

    def handle_command(self, command: str) -> None:
        parts = shlex.split(command)
        if not parts:
            return
        action = parts[0].lower()
        args = parts[1:]
        if action in {"help", "?"}:
            self._print_help()
        elif action == "state":
            self._print_state()
        elif action == "board":
            self._print_board()
        elif action == "corners":
            self._print_corners()
        elif action == "edges":
            self._print_edges()
        elif action == "actions":
            self._print_actions()
        elif action == "roll":
            value = self.game.roll_dice()
            self.console.print(f"Брошено: {value}")
        elif action == "place":
            corner = int(args[0])
            road = int(args[1])
            self.game.place_initial_settlement(corner, road)
        elif action == "build":
            self._handle_build(args)
        elif action == "buy":
            if args and args[0] == "dev":
                card = self.game.buy_development_card()
                self.console.print(f"Вы купили карту {card.value}.")
        elif action == "play":
            self._handle_play(args)
        elif action == "trade":
            self._handle_trade(args)
        elif action == "move":
            if args and args[0] == "robber":
                hex_id = int(args[1])
                victim = args[2] if len(args) > 2 else None
                self.game.move_robber(hex_id, victim)
        elif action == "discard":
            player = args[0]
            losses = self._parse_resource_map(args[1:])
            self.game.discard_cards(player, losses)
        elif action == "end":
            self.game.end_turn()
        else:
            self.console.print("[yellow]Неизвестная команда[/yellow]")

    def _resolve_bot_discards(self) -> None:
        if not self.game.pending_discards:
            return
        updated = True
        while updated:
            updated = False
            legal_actions = self.env.legal_actions()
            for name in list(self.game.pending_discards.keys()):
                if name not in self.bot_agents:
                    continue
                discard_actions = [
                    action
                    for action in legal_actions
                    if action.type == ActionType.DISCARD and action.payload.get("player") == name
                ]
                if not discard_actions:
                    continue
                agent = self.bot_agents[name]
                chosen = agent.choose_action(self.env, discard_actions)
                self.console.print(f"[cyan]{name} (бот) сбрасывает {chosen.payload}[/cyan]")
                self.env.step(chosen)
                updated = True
                break

    def _run_bot_turn(self, name: str) -> None:
        agent = self.bot_agents[name]
        self.console.print(f"[cyan]{name} (бот) начинает ход[/cyan]")
        while True:
            legal_actions = self.env.legal_actions()
            if not legal_actions:
                break
            actions = [
                action
                for action in legal_actions
                if action.type != ActionType.DISCARD
                or action.payload.get("player") == name
            ]
            if not actions:
                break
            action = agent.choose_action(self.env, actions)
            self.console.print(
                f"[cyan]{name} (бот) выполняет {action.type.value} {action.payload}[/cyan]"
            )
            self.env.step(action)
            if (
                self.game.winner
                or self.game.current_player.name != name
                or action.type == ActionType.END_TURN
                or self.game.phase != Phase.MAIN
            ):
                break

    def _handle_build(self, args: List[str]) -> None:
        if not args:
            raise ValueError("Укажите что строим.")
        target = args[0]
        if target == "settlement":
            self.game.build_settlement(int(args[1]))
        elif target == "city":
            self.game.build_city(int(args[1]))
        elif target == "road":
            self.game.build_road(int(args[1]))
        else:
            raise ValueError("Неизвестный тип постройки.")

    def _handle_play(self, args: List[str]) -> None:
        if not args:
            raise ValueError("Какую карту сыграть?")
        card = args[0]
        if card == "knight":
            hex_id = int(args[1])
            victim = args[2] if len(args) > 2 else None
            self.game.play_knight(hex_id, victim)
        elif card == "road":
            edges = [int(arg) for arg in args[1:3]]
            self.game.play_road_building(edges)
        elif card == "yop":
            resources = [parse_resource(arg) for arg in args[1:3]]
            self.game.play_year_of_plenty(resources)
        elif card == "monopoly":
            resource = parse_resource(args[1])
            self.game.play_monopoly(resource)
        else:
            raise ValueError("Неизвестная карта.")

    def _handle_trade(self, args: List[str]) -> None:
        if not args:
            return
        mode = args[0]
        if mode == "bank":
            give = parse_resource(args[1])
            receive = parse_resource(args[2])
            amount = int(args[3]) if len(args) > 3 else 1
            self.game.trade_with_bank(give, receive, amount)
        elif mode == "player":
            target = args[1]
            try:
                give_index = args.index("give")
                receive_index = args.index("receive")
            except ValueError:
                raise ValueError("Используйте 'trade player <name> give ... receive ...'")
            offer = self._parse_resource_map(args[give_index + 1 : receive_index])
            request = self._parse_resource_map(args[receive_index + 1 :])
            self.game.trade_with_player(target, offer, request)
        else:
            raise ValueError("Неизвестный тип обмена.")

    def _parse_resource_map(self, tokens: List[str]) -> Dict[Resource, int]:
        mapping: Dict[Resource, int] = {}
        idx = 0
        while idx < len(tokens):
            resource = parse_resource(tokens[idx])
            amount = int(tokens[idx + 1])
            mapping[resource] = amount
            idx += 2
        return mapping

    def _print_state(self) -> None:
        table = Table(title="Состояние игроков")
        table.add_column("Игрок")
        table.add_column("Очки")
        table.add_column("Ресурсы")
        table.add_column("Постройки")
        for player in self.game.players:
            resources = ", ".join(
                f"{res.value}:{count}"
                for res, count in player.resources.items()
                if count
            ) or "нет"
            buildings = f"S:{len(player.settlements)} C:{len(player.cities)} R:{len(player.roads)}"
            table.add_row(
                player.name,
                str(player.victory_points()),
                resources,
                buildings,
            )
        self.console.print(table)

    def _print_board(self) -> None:
        table = Table(title="Гексы")
        table.add_column("ID")
        table.add_column("Ресурс")
        table.add_column("Число")
        table.add_column("Разбойник")
        for tile in self.game.board.hexes.values():
            table.add_row(
                str(tile.id),
                tile.resource.value,
                str(tile.number_token or "-"),
                "Да" if tile.has_robber else "",
            )
        self.console.print(table)

    def _print_corners(self) -> None:
        table = Table(title="Перекрестки")
        table.add_column("ID")
        table.add_column("Владелец")
        table.add_column("Тип")
        table.add_column("Порт")
        for corner_id in self.game.board.corners:
            corner = self.game.board.corners[corner_id]
            label = self.game.board.corner_label(corner_id)
            port = corner.port.resource.value if corner.port and corner.port.resource else (corner.port.kind.value if corner.port else "-")
            table.add_row(
                str(label),
                corner.owner or "-",
                corner.building or "-",
                port,
            )
        self.console.print(table)

    def _print_edges(self) -> None:
        table = Table(title="Дороги")
        table.add_column("ID")
        table.add_column("Владелец")
        for edge_id in self.game.board.edges:
            edge = self.game.board.edges[edge_id]
            label = self.game.board.edge_label(edge_id)
            table.add_row(str(label), edge.owner or "-")
        self.console.print(table)

    def _print_actions(self) -> None:
        actions = self.env.legal_actions()
        table = Table(title="Доступные действия")
        table.add_column("Тип")
        table.add_column("Детали")
        for action in actions:
            table.add_row(action.type.value, str(action.payload))
        self.console.print(table)

    def _print_help(self) -> None:
        self.console.print(
            """[bold]Команды[/bold]:
- state: показать состояние
- board/corners/edges: показать карту
- actions: возможные действия
- roll: бросок кубиков
- place <corner> <road>: стартовая постройка
- build settlement|city|road <id>
- buy dev: купить карту развития
- play knight|road|yop|monopoly ...
- trade bank <give> <receive> [amount]
- trade player <name> give ... receive ...
- move robber <hex> [victim]
- discard <player> <res> <amount> ...
- end: завершить ход
- quit: выйти"""
        )


@app.command()
def play(
    players: str = typer.Argument(..., help="Список имён игроков через запятую"),
    seed: Optional[int] = typer.Option(None, help="Seed для генератора"),
    random_bot: List[str] = typer.Option(
        [],
        "--random-bot",
        help="Имя игрока, которым управляет случайный бот. Можно повторять.",
    ),
) -> None:
    names = [name.strip() for name in players.split(",") if name.strip()]
    if len(names) < 3 or len(names) > 4:
        raise typer.BadParameter("нужно от 3 до 4 игроков")
    config = GameConfig(player_names=names, seed=seed)
    bots: Dict[str, Agent] = {}
    for bot_name in random_bot:
        if bot_name not in names:
            raise typer.BadParameter(f"бот {bot_name} не входит в список игроков")
        bots[bot_name] = RandomAgent()
    controller = CLIController(Game(config), bots=bots)
    controller.run()


if __name__ == "__main__":
    app()
