from __future__ import annotations

import os

from rich import box
from rich.align import Align
from rich.console import Console, Group, RenderableType
from rich.panel import Panel
from rich.spinner import Spinner
from rich.text import Text

from openpoker.types import (
    ActionAppliedPayload,
    ActionRequiredPayload,
    ActionType,
    AvailableAction,
    EngineErrorPayload,
    EngineEvent,
    EngineEventType,
    GameCreatedPayload,
    GameOverPayload,
    GameSnapshot,
    PlayerEliminatedPayload,
    PlayerSnapshot,
    PlayerStatus,
    PokerAction,
    ShowdownPayload,
    StreetChangedPayload,
)

console = Console()

GOLD = "gold1"
FELT = "green4"
MUTED = "grey50"
WHITE = "white"
RED = "red3"
GREEN = "green4"
CYAN = "cyan"
BLUE = "blue"


def clear_screen() -> None:
    os.system("clear" if os.name != "nt" else "cls")


SUITS: dict[str, tuple[str, str]] = {
    "h": ("♥", RED),
    "d": ("♦", RED),
    "c": ("♣", WHITE),
    "s": ("♠", WHITE),
}

RANKS: dict[str, str] = {
    "2": "2",
    "3": "3",
    "4": "4",
    "5": "5",
    "6": "6",
    "7": "7",
    "8": "8",
    "9": "9",
    "T": "10",
    "J": "J",
    "Q": "Q",
    "K": "K",
    "A": "A",
}


def _parse_card(card_str: str) -> tuple[str, str]:
    card_str = card_str.strip("()[] ")
    if not card_str:
        return "?", "h"
    if card_str.startswith("10") and len(card_str) == 3:
        return "T", card_str[2].lower()
    rank = card_str[0].upper()
    suit = card_str[1].lower() if len(card_str) > 1 else "h"
    return rank, suit


def _render_card_lines(card_str: str, hidden: bool = False) -> list[str]:
    if hidden:
        return [
            f"[{BLUE}]┌─────────┐[/]",
            f"[{BLUE}]│░░░░░░░░░│[/]",
            f"[{BLUE}]│░░░░░░░░░│[/]",
            f"[{BLUE}]│░░░░░░░░░│[/]",
            f"[{BLUE}]│░░░░░░░░░│[/]",
            f"[{BLUE}]│░░░░░░░░░│[/]",
            f"[{BLUE}]└─────────┘[/]",
        ]

    rank, suit = _parse_card(card_str)
    suit_symbol, suit_color = SUITS.get(suit, ("?", WHITE))
    rank_display = RANKS.get(rank, rank)
    top_rank = rank_display.ljust(2)
    bottom_rank = rank_display.rjust(2)
    return [
        f"[{MUTED}]┌─────────┐[/]",
        f"[{MUTED}]│[/] [{suit_color}]{top_rank}[/]    [{suit_color}]{suit_symbol}[/] [{MUTED}]│[/]",
        f"[{MUTED}]│[/]         [{MUTED}]│[/]",
        f"[{MUTED}]│[/]    [{suit_color}]{suit_symbol}[/]    [{MUTED}]│[/]",
        f"[{MUTED}]│[/]         [{MUTED}]│[/]",
        f"[{MUTED}]│[/] [{suit_color}]{suit_symbol}[/]    [{suit_color}]{bottom_rank}[/] [{MUTED}]│[/]",
        f"[{MUTED}]└─────────┘[/]",
    ]


def _render_cards_markup(cards: list[str], hidden: bool = False) -> str:
    if not cards:
        return f"[{MUTED}]No cards[/]"
    all_lines = [_render_card_lines(card, hidden=hidden) for card in cards]
    rows: list[str] = []
    for row_idx in range(7):
        rows.append("  ".join(card[row_idx] for card in all_lines))
    return "\n".join(rows)


def _role_badges(is_dealer: bool, is_small_blind: bool, is_big_blind: bool) -> str:
    badges: list[str] = []
    if is_dealer:
        badges.append(f"[{GOLD}]D[/]")
    if is_small_blind:
        badges.append(f"[{MUTED}]SB[/]")
    if is_big_blind:
        badges.append(f"[{MUTED}]BB[/]")
    return " ".join(badges)


def _status_badge(status: PlayerStatus) -> str:
    if status == PlayerStatus.FOLDED:
        return f"[white on {RED}] FOLDED [/]"
    if status == PlayerStatus.ALL_IN:
        return "[black on gold1] ALL IN [/]"
    if status == PlayerStatus.SITTING_OUT:
        return f"[white on {MUTED}] OUT [/]"
    return ""


def format_event_message(event: EngineEvent) -> str | None:
    event_type = event.event_type
    payload = event.payload

    if event_type == EngineEventType.GAME_CREATED:
        assert isinstance(payload, GameCreatedPayload)
        return f"Game created: {payload.game_name}"
    if event_type == EngineEventType.HAND_STARTED:
        return f"Hand {event.hand_number} started"
    if event_type == EngineEventType.ACTION_REQUIRED:
        assert isinstance(payload, ActionRequiredPayload)
        return f"Waiting for {payload.player_name}..."
    if event_type == EngineEventType.ACTION_APPLIED:
        assert isinstance(payload, ActionAppliedPayload)
        amount_text = f" {payload.amount}" if payload.amount is not None else ""
        return f"{payload.player_name} {payload.action_type.value}{amount_text}"
    if event_type == EngineEventType.STREET_CHANGED:
        assert isinstance(payload, StreetChangedPayload)
        return f"Street: {payload.street.value}"
    if event_type == EngineEventType.SHOWDOWN:
        assert isinstance(payload, ShowdownPayload)
        lines = ["Showdown"]
        for result in payload.results:
            lines.append(f"  {result.name} wins ${result.amount} ({result.hand})")
        return "\n".join(lines)
    if event_type == EngineEventType.PLAYER_ELIMINATED:
        assert isinstance(payload, PlayerEliminatedPayload)
        return f"Eliminated: {payload.player_name}"
    if event_type == EngineEventType.GAME_OVER:
        assert isinstance(payload, GameOverPayload)
        return f"Winner: {payload.winner_name}"
    if event_type == EngineEventType.AI_DECISION:
        return None
    if event_type == EngineEventType.ENGINE_ERROR:
        assert isinstance(payload, EngineErrorPayload)
        return f"Error: {payload.message}"
    return None


def _build_player_panel(
    player: PlayerSnapshot,
    snapshot: GameSnapshot,
    human_player_id: str,
    is_showdown: bool,
    result_by_name: dict[str, list[str]],
    waiting_for: str | None,
) -> Panel:
    if waiting_for is not None and player.id == waiting_for:
        marker = f"[{CYAN}]>>>[/] "
    elif snapshot.current_actor_id == player.id:
        marker = f"[{GOLD}]>>>[/] "
    else:
        marker = "  "

    human_label = " (YOU)" if player.id == human_player_id else ""
    roles = _role_badges(player.is_dealer, player.is_small_blind, player.is_big_blind)
    status_chip = _status_badge(player.status)
    model_text = ""
    if player.model:
        provider = player.provider.value if player.provider else "ai"
        model_text = f"[{MUTED}] ({provider}:{player.model})[/]"
    header = f"{marker}[{WHITE}]{player.name}{human_label}[/] {roles} {status_chip} {model_text}"

    action_text = ""
    if player.current_bet > 0:
        action_text = f"  [{MUTED}]Bet[/] [{WHITE}]${player.current_bet}[/]"
    elif player.last_action and player.status not in (
        PlayerStatus.FOLDED,
        PlayerStatus.SITTING_OUT,
    ):
        action_text = f"  [{MUTED}]{player.last_action.value}[/]"
    details = f"[{MUTED}]Stack[/] [{GOLD}]${player.stack}[/]{action_text}"

    win_lines = result_by_name.get(player.name, [])
    if win_lines:
        details += "\n" + "\n".join(win_lines)

    show_cards = is_showdown and player.hole_cards and player.status != PlayerStatus.FOLDED
    if show_cards:
        cards_markup = _render_cards_markup(player.hole_cards)
        body = header.strip() + "\n" + details + "\n" + cards_markup
    else:
        body = header.strip() + "\n" + details

    border = GOLD if player.id == human_player_id else MUTED
    return Panel(
        Text.from_markup(body),
        border_style=border,
        box=box.SQUARE,
        padding=(0, 1),
    )


def build_snapshot_renderable(
    snapshot: GameSnapshot,
    human_player_id: str,
    event_log: list[str],
    status_message: str | None = None,
    waiting_for: str | None = None,
) -> Group:
    parts: list[RenderableType] = []

    title = Text()
    title.append("♠ ", style=WHITE)
    title.append("♥ ", style=RED)
    title.append("OPENPOKER", style=f"bold {GOLD}")
    title.append(" ♦ ", style=RED)
    title.append("♣", style=WHITE)
    parts.append(Align.center(title))
    parts.append(Text(""))

    board_markup = _render_cards_markup(snapshot.board_cards, hidden=False)
    parts.append(
        Panel(
            Text.from_markup(board_markup),
            title=f"[{WHITE}]Board[/]",
            border_style=FELT,
            box=box.SQUARE,
            padding=(0, 2),
        )
    )
    parts.append(
        Text.from_markup(
            f"[{MUTED}]Hand #{snapshot.hand_number}[/]"
            f" [{MUTED}]|[/] "
            f"[{GOLD}]Pot ${snapshot.pot}[/]"
            f" [{MUTED}]|[/] "
            f"[{WHITE}]Bet ${snapshot.current_bet}[/]"
            f" [{MUTED}]|[/] "
            f"[{WHITE}]{snapshot.street.value.upper()}[/]"
        )
    )
    parts.append(Text(""))

    is_showdown = bool(snapshot.showdown_results)
    result_by_name: dict[str, list[str]] = {}
    if is_showdown:
        for result in snapshot.showdown_results:
            result_by_name.setdefault(result.name, []).append(
                f"[{GOLD}]Won ${result.amount}[/] [{WHITE}]({result.hand})[/]"
            )

    for player in snapshot.players:
        panel = _build_player_panel(
            player,
            snapshot,
            human_player_id,
            is_showdown,
            result_by_name,
            waiting_for,
        )
        parts.append(panel)

    parts.append(Text(""))

    if status_message:
        parts.append(Panel(status_message, title="Status", border_style=MUTED, box=box.SQUARE))

    recent = event_log[-8:]
    if recent or waiting_for:
        if waiting_for and recent and recent[-1].startswith("Waiting for"):
            waiting_name = snapshot.current_actor_name or "..."
            spinner = Spinner("dots", text=f"{waiting_name} is thinking", style=CYAN)
            event_lines = recent[:-1]
            if event_lines:
                events_content = Group(
                    Text.from_markup("\n".join(event_lines)),
                    Text(""),
                    spinner,
                )
            else:
                events_content = spinner
        else:
            events_content = Text.from_markup("\n".join(recent))

        parts.append(
            Panel(
                events_content,
                title="Events",
                border_style=MUTED,
                box=box.SQUARE,
                padding=(0, 1),
            )
        )

    return Group(*parts)


def render_snapshot(
    snapshot: GameSnapshot,
    human_player_id: str,
    event_log: list[str],
    status_message: str | None = None,
) -> None:
    clear_screen()
    renderable = build_snapshot_renderable(
        snapshot,
        human_player_id,
        event_log,
        status_message,
    )
    console.print(renderable)


def prompt_action(
    available_actions: list[AvailableAction],
    hero_hole_cards: list[str] | None = None,
) -> PokerAction:
    if hero_hole_cards:
        console.print(
            Panel(
                Text.from_markup(_render_cards_markup(hero_hole_cards)),
                title=f"[{GOLD}]Your hand[/]",
                border_style=GOLD,
                box=box.SQUARE,
                padding=(0, 2),
            )
        )

    action_info: dict[str, dict[str, int | None]] = {}
    for action in available_actions:
        action_type = action.action_type.value
        if action_type in ("fold", "check"):
            action_info[action_type] = {"amount": None, "min": None, "max": None}
        elif action_type == "call":
            amount = action.min_amount or 0
            action_info[action_type] = {"amount": amount, "min": amount, "max": amount}
        elif action_type in ("bet", "raise"):
            action_info[action_type] = {
                "amount": None,
                "min": action.min_amount,
                "max": action.max_amount,
            }

    labels: list[str] = []
    if "fold" in action_info:
        labels.append(f"[{RED}]fold[/]")
    if "check" in action_info:
        labels.append(f"[{GREEN}]check[/]")
    if "call" in action_info:
        labels.append(f"[{GREEN}]call {action_info['call']['amount']}[/]")
    if "bet" in action_info:
        labels.append(f"[{CYAN}]bet {action_info['bet']['min']}-{action_info['bet']['max']}[/]")
    if "raise" in action_info:
        labels.append(f"[{CYAN}]raise {action_info['raise']['min']}-{action_info['raise']['max']}[/]")
    console.print(Panel(" [dim]│[/] ".join(labels), title="Actions", border_style=GREEN, box=box.SQUARE))

    while True:
        raw = console.input(f"[{GOLD}]>[/] ").strip().lower()
        if not raw:
            continue

        parts = raw.split()
        action_word = parts[0]
        if action_word not in action_info:
            console.print(f"[{RED}]Invalid action[/{RED}]")
            continue

        if action_word in ("fold", "check", "call"):
            if action_word == "fold":
                return PokerAction(action_type=ActionType.FOLD)
            if action_word == "check":
                return PokerAction(action_type=ActionType.CHECK)
            return PokerAction(action_type=ActionType.CALL)

        limits = action_info[action_word]
        min_amt = int(limits["min"] or 0)
        max_amt = int(limits["max"] or 0)
        amount: int | None = None
        if len(parts) >= 2 and parts[1].replace("$", "").isdigit():
            amount = int(parts[1].replace("$", ""))
        if amount is None:
            amount_raw = console.input(f"[{WHITE}]Amount {min_amt}-{max_amt}[/{WHITE}] ").strip()
            if amount_raw.replace("$", "").isdigit():
                amount = int(amount_raw.replace("$", ""))
        if amount is None:
            console.print(f"[{RED}]Amount required[/{RED}]")
            continue
        if amount < min_amt or amount > max_amt:
            console.print(f"[{RED}]Amount must be {min_amt}-{max_amt}[/{RED}]")
            continue

        if action_word == "bet":
            return PokerAction(action_type=ActionType.BET, amount=amount)
        return PokerAction(action_type=ActionType.RAISE, amount=amount)
