"""Poker math helpers: preflop hand strength, pot odds, equity estimation."""

from __future__ import annotations
import random
from typing import Optional

from .state import Card

# ── Preflop hand strength table ──
# Chen formula approximation: returns 0.0–1.0 normalized strength for a 2-card hand.
# Based on the 169 canonical hand classes.

_CHEN_PAIRS = {14: 1.0, 13: 0.95, 12: 0.9, 11: 0.85, 10: 0.8, 9: 0.7, 8: 0.6,
               7: 0.5, 6: 0.45, 5: 0.4, 4: 0.35, 3: 0.3, 2: 0.25}


def preflop_strength(hole_cards: list[Card]) -> float:
    """Score a 2-card hand from 0.0 (junk) to 1.0 (AA).

    Uses calibrated equity approximation. Suited hands get a 0.05 bump,
    connected hands get a connectivity bonus. Pairs scored from table.
    Weak kickers with high cards are penalized for domination risk.
    """
    if len(hole_cards) < 2:
        return 0.0
    r1 = hole_cards[0].numeric_rank
    r2 = hole_cards[1].numeric_rank
    high, low = max(r1, r2), min(r1, r2)
    suited = hole_cards[0].suit == hole_cards[1].suit

    if high == low:
        return _CHEN_PAIRS.get(high, 0.25)

    gap = high - low
    score = 0.0

    # High card contribution
    score += 0.10 + ((high - 2) / 12) * 0.30

    # Low card (kicker) contribution — weighted for kicker quality
    score += ((low - 2) / 12) * 0.22

    # Suited bonus
    if suited:
        score += 0.05

    # Connectivity bonus (straight potential)
    if gap == 1:
        score += 0.04
    elif gap == 2:
        score += 0.02
    elif gap == 3:
        score += 0.01

    # Both broadway bonus
    if high >= 10 and low >= 10:
        score += 0.08

    # Ace with decent kicker
    if high == 14 and low >= 10:
        score += 0.05
    elif high == 14 and low >= 7:
        score += 0.02

    # Gap penalty — harsh for disconnected hands (domination risk)
    if gap >= 10:
        score -= 0.14
    elif gap >= 8:
        score -= 0.10
    elif gap >= 6:
        score -= 0.07
    elif gap >= 5:
        score -= 0.04
    elif gap >= 4:
        score -= 0.02

    # Low kicker penalty — weak kicker with high card = dominated
    if low <= 6 and high >= 10:
        score -= 0.06
    elif low <= 8 and high >= 12:
        score -= 0.03

    return max(0.0, min(1.0, score))


# ── Pot odds ──

def pot_odds(cost_to_call: int, pot: int) -> float:
    """Required equity to call profitably. Returns 0.0 if nothing to call."""
    if cost_to_call <= 0:
        return 0.0
    return cost_to_call / (pot + cost_to_call)


# ── Equity estimation via Monte Carlo ──

_ALL_RANKS = list(range(2, 15))
_ALL_SUITS = ["s", "h", "d", "c"]
_FULL_DECK = [(r, s) for r in _ALL_RANKS for s in _ALL_SUITS]


def _card_key(c: Card) -> tuple:
    return (c.numeric_rank, c.suit.lower()[0] if c.suit else "?")


def _best_five(seven: list[tuple]) -> int:
    """Evaluate 7-card hand, return a comparable integer (higher = better).

    Uses a simplified evaluator: detects flush, straight, pairs/trips/quads.
    Not a full poker hand evaluator — good enough for equity estimation.
    """
    from itertools import combinations
    best = 0
    for combo in combinations(seven, 5):
        score = _eval_five(list(combo))
        if score > best:
            best = score
    return best


def _eval_five(five: list[tuple]) -> int:
    """Score a 5-card hand. Returns an integer for comparison."""
    ranks = sorted([c[0] for c in five], reverse=True)
    suits = [c[1] for c in five]
    is_flush = len(set(suits)) == 1

    # Check straight
    unique_ranks = sorted(set(ranks), reverse=True)
    is_straight = False
    straight_high = 0
    if len(unique_ranks) >= 5:
        for i in range(len(unique_ranks) - 4):
            if unique_ranks[i] - unique_ranks[i + 4] == 4:
                is_straight = True
                straight_high = unique_ranks[i]
                break
        # Wheel (A-2-3-4-5)
        if not is_straight and 14 in unique_ranks and set([2, 3, 4, 5]).issubset(set(unique_ranks)):
            is_straight = True
            straight_high = 5

    # Count rank frequencies
    from collections import Counter
    counts = Counter(ranks)
    freq = sorted(counts.values(), reverse=True)
    freq_ranks = sorted(counts.keys(), key=lambda r: (counts[r], r), reverse=True)

    # Hand categories (8=SF, 7=quads, 6=FH, 5=flush, 4=straight, 3=trips, 2=two-pair, 1=pair, 0=high)
    if is_flush and is_straight:
        return 8_00_00_00_00 + straight_high
    if freq[0] == 4:
        return 7_00_00_00_00 + freq_ranks[0] * 100 + freq_ranks[1]
    if freq[0] == 3 and freq[1] == 2:
        return 6_00_00_00_00 + freq_ranks[0] * 100 + freq_ranks[1]
    if is_flush:
        v = sum(r * (15 ** i) for i, r in enumerate(reversed(ranks[:5])))
        return 5_00_00_00_00 + v
    if is_straight:
        return 4_00_00_00_00 + straight_high
    if freq[0] == 3:
        kickers = [r for r in ranks if counts[r] != 3][:2]
        return 3_00_00_00_00 + freq_ranks[0] * 10000 + sum(k * (15 ** i) for i, k in enumerate(reversed(kickers)))
    if freq[0] == 2 and freq[1] == 2:
        pairs = sorted([r for r in counts if counts[r] == 2], reverse=True)
        kicker = max(r for r in ranks if counts[r] == 1)
        return 2_00_00_00_00 + pairs[0] * 10000 + pairs[1] * 100 + kicker
    if freq[0] == 2:
        pair_rank = freq_ranks[0]
        kickers = sorted([r for r in ranks if r != pair_rank], reverse=True)[:3]
        return 1_00_00_00_00 + pair_rank * 100000 + sum(k * (15 ** i) for i, k in enumerate(reversed(kickers)))
    # High card
    return sum(r * (15 ** i) for i, r in enumerate(reversed(ranks[:5])))


def equity_estimate(
    hole_cards: list[Card],
    board: list[Card],
    n_opponents: int = 1,
    samples: int = 500,
) -> float:
    """Monte Carlo equity estimation.

    Deals random opponent hands and remaining board cards, evaluates
    all hands, counts wins. Returns 0.0–1.0 equity.

    Accuracy improves with more samples. 500 samples gives ~2% error
    for heads-up, which is more than sufficient for LLM prompt context.
    """
    if len(hole_cards) < 2:
        return 0.5

    hero_keys = {_card_key(c) for c in hole_cards}
    board_keys = {_card_key(c) for c in board}
    dead = hero_keys | board_keys
    live_deck = [c for c in _FULL_DECK if c not in dead]

    hero_tuples = [_card_key(c) for c in hole_cards]
    board_tuples = [_card_key(c) for c in board]
    cards_to_deal = 5 - len(board_tuples)

    wins = 0
    ties = 0

    for _ in range(samples):
        drawn = random.sample(live_deck, cards_to_deal + 2 * n_opponents)
        full_board = board_tuples + drawn[:cards_to_deal]
        hero_seven = hero_tuples + full_board
        hero_score = _best_five(hero_seven)

        best_opp = 0
        for opp_i in range(n_opponents):
            opp_start = cards_to_deal + opp_i * 2
            opp_cards = drawn[opp_start:opp_start + 2]
            opp_seven = list(opp_cards) + full_board
            opp_score = _best_five(opp_seven)
            if opp_score > best_opp:
                best_opp = opp_score

        if hero_score > best_opp:
            wins += 1
        elif hero_score == best_opp:
            ties += 1

    return (wins + ties * 0.5) / samples
