"""LLMAgent — drop-in reasoning agent powered by OpenAI (or compatible API)."""

from __future__ import annotations
import json
import re
from typing import Optional

from .agent import BaseAgent
from .state import GameState, Action
from .strategy import preflop_strength, equity_estimate, pot_odds


# ── System prompts by style ──
# Serious poker reasoning first. Personality layered on top, not replacing competence.

STYLE_PROMPTS: dict[str, str] = {
    "shark": (
        "You are a strong, balanced heads-up no-limit hold'em player. "
        "You open 20-25% of hands preflop. You mix value bets and bluffs at sound frequencies. "
        "You pay attention to opponent tendencies and exploit weaknesses. "
        "You think in ranges, not individual hands. You size bets based on board texture — "
        "small on dry boards, large on wet boards. You plan bet sizing across all streets."
    ),
    "tag": (
        "You are a tight-aggressive player. You open 18-22% of hands, tighter from early position. "
        "When you enter a pot, you take control with continuation bets ~70% of the time. "
        "You fold to 3-bets without premium holdings. Fundamentally sound, hard to bluff."
    ),
    "lag": (
        "You are a loose-aggressive player. You open 30-35% of hands and 3-bet light frequently. "
        "Your wide range is a weapon — opponents can never put you on a hand. "
        "You apply maximum pressure with semi-bluffs and barrel on cards that complete your perceived range."
    ),
    "rock": (
        "You are an extremely tight, patient player. You play only premium hands: "
        "pocket pairs 99+, AK, AQ, AJs+. When you play, you bet for value relentlessly. "
        "You rarely bluff because your range is strong enough to generate value naturally."
    ),
    "balanced": (
        "You are a well-rounded player. You adjust your strategy based on opponent tendencies. "
        "Against aggressive opponents, you tighten up and trap. Against passive opponents, "
        "you widen your range and apply pressure. You aim for GTO-adjacent play as a baseline."
    ),
}

DEFAULT_STYLE = "shark"


def _build_system_prompt(style: str, custom_instructions: str = "") -> str:
    base = STYLE_PROMPTS.get(style, STYLE_PROMPTS[DEFAULT_STYLE])
    prompt = (
        f"You are a poker AI agent playing heads-up no-limit Texas hold'em.\n\n"
        f"STRATEGY:\n{base}\n\n"
        f"DECISION FRAMEWORK:\n"
        f"1. Evaluate your hand strength relative to the board\n"
        f"2. Consider pot odds and implied odds\n"
        f"3. Factor in position (IP vs OOP)\n"
        f"4. Read opponent tendencies from their stats and recent actions\n"
        f"5. Choose the highest-EV action\n\n"
        f"RULES:\n"
        f"- Respond with ONLY a JSON object\n"
        f'- Format: {{"action": "fold|check|call|raise", "amount": 0, "reasoning": "one sentence"}}\n'
        f"- For raise, amount must be an integer within the legal raise range\n"
        f"- For fold/check/call, amount is ignored\n"
        f"- To go all-in, use the maximum raise amount"
    )
    if custom_instructions:
        prompt += f"\n\nADDITIONAL INSTRUCTIONS:\n{custom_instructions}"
    return prompt


def _build_action_prompt(
    state: GameState,
    opponent_summary: str = "",
    equity: Optional[float] = None,
    preflop_str: Optional[float] = None,
) -> str:
    """Build the per-action user prompt from game state."""
    lines = [
        "GAME STATE:",
        state.summary(),
    ]

    # Add computed analysis
    analysis = []
    if preflop_str is not None:
        analysis.append(f"Preflop hand strength: {preflop_str:.0%}")
    if equity is not None:
        analysis.append(f"Estimated equity: {equity:.0%}")
    odds = state.pot_odds()
    if odds > 0:
        analysis.append(f"Pot odds: {odds:.0%}")
    analysis.append(f"Effective stack: {state.effective_stack():.0f} BB")
    analysis.append(f"Board texture: {state.board_texture()}")

    if analysis:
        lines.append("")
        lines.append("ANALYSIS:")
        for a in analysis:
            lines.append(f"  {a}")

    if opponent_summary:
        lines.append("")
        lines.append(f"OPPONENT PROFILE:\n  {opponent_summary}")

    lines.append("")
    lines.append("Choose your action.")
    return "\n".join(lines)


def _parse_llm_response(text: str, state: GameState) -> tuple[Action, str]:
    """Parse LLM response into (Action, reasoning). Robust to various output formats."""
    text = text.strip()
    reasoning = ""

    # Try JSON parse — use a balanced-brace regex to handle nested braces in reasoning
    json_match = re.search(r'\{(?:[^{}]|\{[^{}]*\})*\}', text)
    if json_match:
        try:
            obj = json.loads(json_match.group())
            action_type = obj.get("action", "").lower().strip()
            amount = obj.get("amount", 0)
            reasoning = obj.get("reasoning", "")

            if action_type == "allin" or action_type == "all_in" or action_type == "all-in":
                _, max_r = state.raise_range
                if max_r > 0:
                    return Action.raise_to(max_r), reasoning
                action = Action.call() if state.can("call") else Action.check() if state.can("check") else Action.fold()
                return action, reasoning

            if action_type == "raise" and state.can("raise"):
                min_r, max_r = state.raise_range
                amt = int(amount) if amount else min_r
                amt = max(min_r, min(amt, max_r))
                return Action.raise_to(amt), reasoning
            if action_type == "call" and state.can("call"):
                return Action.call(), reasoning
            if action_type == "check" and state.can("check"):
                return Action.check(), reasoning
            if action_type == "fold":
                return Action.fold(), reasoning
        except (json.JSONDecodeError, ValueError, TypeError):
            pass

    # Fallback: look for action keywords in raw text
    lower = text.lower()
    if "raise" in lower and state.can("raise"):
        nums = re.findall(r'\d+', text)
        min_r, max_r = state.raise_range
        if nums:
            amt = max(min_r, min(int(nums[-1]), max_r))
            return Action.raise_to(amt), reasoning
        return Action.raise_to(min_r), reasoning
    if "call" in lower and state.can("call"):
        return Action.call(), reasoning
    if "check" in lower and state.can("check"):
        return Action.check(), reasoning
    if "fold" in lower:
        return Action.fold(), reasoning

    # Ultimate fallback
    if state.can("check"):
        return Action.check(), reasoning
    return Action.fold(), reasoning


class LLMAgent(BaseAgent):
    """A poker agent that uses an LLM for decision-making.

    This is the hero path — install, configure, run:

        from agentpoker import LLMAgent

        agent = LLMAgent(
            model="gpt-4o",
            openai_api_key="sk-...",
            api_key="your-agentpoker-key",
            agent_id="your-agent-id",
        )
        agent.run()

    The agent builds structured prompts with game state, computed analysis
    (equity, pot odds, board texture), and opponent tracking (VPIP/PFR/AF).
    The LLM reasons about the hand and returns a JSON action.
    """

    def __init__(
        self,
        model: str = "gpt-4o",
        openai_api_key: Optional[str] = None,
        style: str = "shark",
        custom_instructions: str = "",
        compute_equity: bool = True,
        equity_samples: int = 500,
        **kwargs,
    ):
        """
        Args:
            model: OpenAI model name (e.g., "gpt-4o", "gpt-4o-mini")
            openai_api_key: OpenAI API key. If None, reads from OPENAI_API_KEY env var.
            style: Playing style — "shark", "tag", "lag", "rock", "balanced"
            custom_instructions: Extra text appended to system prompt
            compute_equity: If True, run Monte Carlo equity before each decision
            equity_samples: Number of MC samples for equity estimation
            **kwargs: Passed to BaseAgent (server, api_key, agent_id, agent_name, etc.)
        """
        super().__init__(**kwargs)
        self.model = model
        self.style = style
        self.compute_equity = compute_equity
        self.equity_samples = equity_samples
        self._system_prompt = _build_system_prompt(style, custom_instructions)

        import openai
        self._openai = openai.OpenAI(api_key=openai_api_key)

    def decide(self, state: GameState) -> Action:
        """Query the LLM for a poker decision."""
        # Compute analysis
        preflop_str = preflop_strength(state.hole_cards) if state.hole_cards else None

        equity = None
        if self.compute_equity and state.hole_cards and state.street != "preflop":
            equity = equity_estimate(
                state.hole_cards,
                state.board,
                n_opponents=len(state.active_opponents),
                samples=self.equity_samples,
            )

        # Opponent profile
        opp_summary = ""
        for opp in state.opponents:
            profile = self.tracker.profile(opp.name)
            if profile.hands_seen >= 2:
                opp_summary = f"{profile.summary()} — Style: {profile.style_label()}"
                break

        prompt = _build_action_prompt(state, opp_summary, equity, preflop_str)

        try:
            response = self._openai.chat.completions.create(
                model=self.model,
                messages=[
                    {"role": "system", "content": self._system_prompt},
                    {"role": "user", "content": prompt},
                ],
                max_tokens=150,
                temperature=0.3,
            )
            text = response.choices[0].message.content or ""
            action, reasoning = _parse_llm_response(text, state)
            self._last_reason = reasoning
            return action

        except Exception as e:
            if self.client.verbose:
                self.client._log(f"LLM error: {e}, falling back to heuristic")
            return self._heuristic_fallback(state, preflop_str)

    def _heuristic_fallback(self, state: GameState, preflop_str: Optional[float]) -> Action:
        """Simple heuristic when LLM is unavailable or times out."""
        strength = preflop_str or 0.3
        if state.street != "preflop" and state.hole_cards:
            eq = equity_estimate(state.hole_cards, state.board, samples=200)
            strength = eq

        pct = f"{strength:.0%} equity"
        if strength >= 0.7 and state.can("raise"):
            self._last_reason = f"strong hand ({pct}), raising for value"
            return state.raise_pot()
        if strength >= 0.4 and state.can("call"):
            self._last_reason = f"decent hand ({pct}), calling"
            return Action.call()
        if state.can("check"):
            self._last_reason = f"free card ({pct})"
            return Action.check()
        if strength >= 0.3 and state.can("call"):
            self._last_reason = f"marginal ({pct}), calling"
            return Action.call()
        self._last_reason = f"weak hand ({pct}), folding"
        return Action.fold()
