"""Typed game state wrapper with computed poker helpers."""

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Optional


@dataclass
class Card:
    rank: str
    suit: str
    display: str

    @property
    def numeric_rank(self) -> int:
        if isinstance(self.rank, (int, float)):
            return int(self.rank)
        m = {"T": 10, "J": 11, "Q": 12, "K": 13, "A": 14}
        s = str(self.rank).upper()
        if s.isdigit():
            return int(s)
        return m.get(s, 0)

    def __repr__(self) -> str:
        return self.display


@dataclass
class Player:
    id: str
    name: str
    chips: int
    folded: bool
    is_all_in: bool
    current_round_bet: int
    total_hand_bet: int
    hole_cards: list[Card] = field(default_factory=list)

    @classmethod
    def from_dict(cls, d: dict) -> Player:
        cards = [Card(c["rank"], c["suit"], c.get("display", "")) for c in (d.get("holeCards") or [])]
        return cls(
            id=d.get("id", ""),
            name=d.get("name", ""),
            chips=d.get("chips", 0),
            folded=d.get("folded", False),
            is_all_in=d.get("isAllIn", False),
            current_round_bet=d.get("currentRoundBet", 0),
            total_hand_bet=d.get("totalHandBet", 0),
            hole_cards=cards,
        )


@dataclass
class Action:
    """Represents an action to send back to the server."""
    type: str
    data: Optional[dict] = None

    def to_dict(self) -> dict:
        d: dict[str, Any] = {"type": self.type}
        if self.data:
            d["data"] = self.data
        return d

    @staticmethod
    def fold() -> Action:
        return Action("fold")

    @staticmethod
    def check() -> Action:
        return Action("check")

    @staticmethod
    def call() -> Action:
        return Action("call")

    @staticmethod
    def raise_to(amount: int) -> Action:
        return Action("raise", {"amount": int(amount)})

    @staticmethod
    def all_in(max_amount: int) -> Action:
        return Action("raise", {"amount": int(max_amount)})


@dataclass
class LegalAction:
    type: str
    data: Optional[dict] = None

    @property
    def min_amount(self) -> int:
        if self.data:
            return int(self.data.get("minAmount", 0))
        return 0

    @property
    def max_amount(self) -> int:
        if self.data:
            return int(self.data.get("maxAmount", 0))
        return 0

    @classmethod
    def from_dict(cls, d: dict) -> LegalAction:
        return cls(type=d.get("type", ""), data=d.get("data"))


class GameState:
    """Typed wrapper around the raw game_state JSON from the server.

    Provides computed helpers for common poker math — pot odds, board texture,
    effective stacks, position awareness.
    """

    def __init__(self, raw_state: dict, raw_legal_actions: list[dict]):
        self._raw = raw_state
        hand = raw_state.get("hand", raw_state)

        self.hand_number: int = hand.get("handNumber", 0)
        self.street: str = hand.get("bettingRound", "preflop")
        self.pot: int = hand.get("pot", 0)
        self.current_bet: int = hand.get("currentBet", 0)
        self.position: str = hand.get("position", "")

        blinds = hand.get("blinds", {})
        self.small_blind: int = blinds.get("small", 0)
        self.big_blind: int = blinds.get("big", 0)

        self.my_index: int = raw_state.get("playerIndex", hand.get("myIndex", 0))

        self.players: list[Player] = [
            Player.from_dict(p) for p in hand.get("players", [])
        ]

        board_raw = hand.get("communityCards", [])
        self.board: list[Card] = [
            Card(c["rank"], c["suit"], c.get("display", "")) for c in board_raw
        ]

        self.action_history: list[str] = hand.get("actionHistory", [])

        street_state = hand.get("streetState", {})
        self.to_call: int = street_state.get("toCall", max(0, self.current_bet - self.me.current_round_bet))
        self.raises_this_street: int = street_state.get("raisesThisStreet", 0)

        board_summary = hand.get("boardSummary", {})
        self._paired_board: bool = board_summary.get("pairedBoard", False)
        self._trips_on_board: bool = board_summary.get("tripsOnBoard", False)

        self.legal_actions: list[LegalAction] = [
            LegalAction.from_dict(a) for a in raw_legal_actions
        ]

    @property
    def me(self) -> Player:
        if 0 <= self.my_index < len(self.players):
            return self.players[self.my_index]
        return Player("", "", 0, False, False, 0, 0)

    @property
    def hole_cards(self) -> list[Card]:
        return self.me.hole_cards

    @property
    def opponents(self) -> list[Player]:
        return [p for i, p in enumerate(self.players) if i != self.my_index and not p.folded]

    @property
    def active_opponents(self) -> list[Player]:
        return [p for p in self.opponents if not p.folded]

    def pot_odds(self) -> float:
        """Cost-to-call / (pot + cost-to-call). Returns 0.0 if nothing to call."""
        if self.to_call <= 0:
            return 0.0
        return self.to_call / (self.pot + self.to_call)

    def effective_stack(self) -> float:
        """Your chips expressed in big blinds."""
        if self.big_blind <= 0:
            return float(self.me.chips)
        return self.me.chips / self.big_blind

    def ip(self) -> bool:
        """True if you are in position (BTN or SB/BTN in heads-up)."""
        return self.position in ("BTN", "SB/BTN", "CO")

    def board_texture(self) -> str:
        """Simple board classification: dry, paired, wet, monotone."""
        if not self.board:
            return "preflop"
        if self._trips_on_board:
            return "trips"
        if self._paired_board:
            return "paired"
        suits = [c.suit for c in self.board]
        if len(set(suits)) == 1:
            return "monotone"
        if suits.count(max(set(suits), key=suits.count)) >= 3:
            return "flush_draw"
        ranks = sorted([c.numeric_rank for c in self.board])
        if len(ranks) >= 3:
            gaps = sum(1 for i in range(len(ranks) - 1) if ranks[i + 1] - ranks[i] <= 2)
            if gaps >= 2:
                return "wet"
        return "dry"

    def can(self, action_type: str) -> bool:
        """Check if an action type is legal."""
        return any(a.type == action_type for a in self.legal_actions)

    def legal(self, action_type: str) -> Optional[LegalAction]:
        """Get a specific legal action by type, or None."""
        for a in self.legal_actions:
            if a.type == action_type:
                return a
        return None

    @property
    def raise_range(self) -> tuple[int, int]:
        """(min_raise, max_raise) or (0, 0) if raise not legal."""
        r = self.legal("raise")
        if r:
            return (r.min_amount, r.max_amount)
        return (0, 0)

    def raise_pot(self) -> Action:
        """Raise to pot-sized bet. Clamped to legal range."""
        min_r, max_r = self.raise_range
        pot_raise = self.pot + self.to_call * 2
        amount = max(min_r, min(pot_raise, max_r))
        return Action.raise_to(amount)

    def raise_min(self) -> Action:
        """Minimum legal raise."""
        min_r, _ = self.raise_range
        return Action.raise_to(min_r)

    def raise_half_pot(self) -> Action:
        """Raise to half-pot. Clamped to legal range."""
        min_r, max_r = self.raise_range
        half_pot = (self.pot + self.to_call) // 2 + self.to_call
        amount = max(min_r, min(half_pot, max_r))
        return Action.raise_to(amount)

    def summary(self) -> str:
        """Human-readable state summary, suitable for LLM prompts."""
        hole = " ".join(c.display for c in self.hole_cards) if self.hole_cards else "??"
        board_str = " ".join(c.display for c in self.board) if self.board else "(none)"
        opps = "; ".join(
            f"{p.name}: {p.chips} chips" for p in self.opponents
        )
        actions = ", ".join(a.type for a in self.legal_actions)
        lines = [
            f"Hand #{self.hand_number}, Street: {self.street}",
            f"Position: {self.position}",
            f"Blinds: {self.small_blind}/{self.big_blind}",
            f"Your cards: {hole}",
            f"Board: {board_str}",
            f"Pot: {self.pot}, To call: {self.to_call}, Your chips: {self.me.chips}",
            f"Opponents: {opps}",
            f"Legal actions: {actions}",
        ]
        if self.can("raise"):
            min_r, max_r = self.raise_range
            lines.append(f"Raise range: [{min_r}, {max_r}]")
        return "\n".join(lines)
