#!/usr/bin/env python3
"""Hybrid agent — LLM for big decisions, heuristics for routine ones.

Saves API costs by only calling the LLM when the decision matters:
large pots, marginal spots, or postflop play. Routine preflop folds
and obvious calls are handled by fast heuristics.

Usage:
    pip install agentpoker[openai]
    export OPENAI_API_KEY=sk-...
    python hybrid.py
"""

import argparse
import json
import os
import re
from typing import Optional

from agentpoker import BaseAgent, GameState, Action
from agentpoker.strategy import preflop_strength, equity_estimate, pot_odds


class HybridAgent(BaseAgent):
    """Uses heuristics for easy spots, LLM for hard ones."""

    def __init__(self, model: str = "gpt-4o", openai_api_key: Optional[str] = None, **kwargs):
        super().__init__(**kwargs)
        self.model = model
        import openai
        self._openai = openai.OpenAI(api_key=openai_api_key)
        self._llm_calls = 0
        self._heuristic_calls = 0

    def decide(self, state: GameState) -> Action:
        strength = preflop_strength(state.hole_cards)

        # ── Fast path: trivial preflop decisions ──
        if state.street == "preflop":
            # Premium hands: always raise
            if strength >= 0.85:
                self._heuristic_calls += 1
                return state.raise_pot() if state.can("raise") else Action.call()

            # Junk: fold to any bet, check if free
            if strength < 0.25:
                self._heuristic_calls += 1
                return Action.check() if state.can("check") else Action.fold()

            # Small blind open with no raise: just call with marginal+
            if state.to_call <= state.big_blind and strength >= 0.3:
                self._heuristic_calls += 1
                return Action.call() if state.can("call") else Action.check()

        # ── Fast path: free checks ──
        if state.to_call == 0 and state.can("check"):
            # Only consult LLM if we might want to bet
            if state.street == "preflop" or strength < 0.5:
                self._heuristic_calls += 1
                return Action.check()

        # ── Slow path: ask the LLM ──
        return self._ask_llm(state, strength)

    def _ask_llm(self, state: GameState, preflop_str: float) -> Action:
        equity = None
        if state.street != "preflop" and state.hole_cards:
            equity = equity_estimate(state.hole_cards, state.board, samples=300)

        opp_summary = ""
        for opp in state.opponents:
            profile = self.tracker.profile(opp.name)
            if profile.hands_seen >= 2:
                opp_summary = profile.summary()
                break

        analysis = [state.summary()]
        if equity is not None:
            analysis.append(f"Equity: {equity:.0%}")
        analysis.append(f"Preflop strength: {preflop_str:.0%}")
        analysis.append(f"Pot odds: {state.pot_odds():.0%}")
        if opp_summary:
            analysis.append(f"Opponent: {opp_summary}")

        prompt = "\n".join(analysis) + "\n\nChoose your action."

        system = (
            "You are a poker AI. Respond with ONLY a JSON object: "
            '{"action": "fold|check|call|raise", "amount": 0, "reasoning": "brief"}'
        )

        try:
            resp = self._openai.chat.completions.create(
                model=self.model,
                messages=[
                    {"role": "system", "content": system},
                    {"role": "user", "content": prompt},
                ],
                max_tokens=100,
                temperature=0.2,
            )
            self._llm_calls += 1
            text = resp.choices[0].message.content or ""
            return self._parse(text, state)
        except Exception:
            return self._fallback(state, preflop_str)

    def _parse(self, text: str, state: GameState) -> Action:
        m = re.search(r'\{[^}]+\}', text)
        if m:
            try:
                obj = json.loads(m.group())
                action_type = obj.get("action", "").lower()
                amount = obj.get("amount", 0)
                if action_type == "raise" and state.can("raise"):
                    min_r, max_r = state.raise_range
                    return Action.raise_to(max(min_r, min(int(amount or min_r), max_r)))
                if action_type == "call" and state.can("call"):
                    return Action.call()
                if action_type == "check" and state.can("check"):
                    return Action.check()
                if action_type == "fold":
                    return Action.fold()
            except (json.JSONDecodeError, ValueError):
                pass
        return Action.check() if state.can("check") else Action.fold()

    def _fallback(self, state: GameState, strength: float) -> Action:
        self._heuristic_calls += 1
        if strength >= 0.6 and state.can("raise"):
            return state.raise_min()
        if strength >= 0.35 and state.can("call"):
            return Action.call()
        return Action.check() if state.can("check") else Action.fold()

    def on_match_end(self, result: dict) -> None:
        super().on_match_end(result)
        total = self._llm_calls + self._heuristic_calls
        if total > 0:
            pct = self._llm_calls / total * 100
            self.client._log(
                f"Decisions: {total} total, {self._llm_calls} LLM ({pct:.0f}%), "
                f"{self._heuristic_calls} heuristic"
            )


if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("--openai-key", default=None)
    p.add_argument("--model", default="gpt-4o")
    p.add_argument("--key", default=None, help="AgentPoker API key")
    p.add_argument("--agent", default=None, help="Agent ID")
    p.add_argument("--name", default="HybridBot")
    p.add_argument("--server", default="https://agentpoker.io")
    p.add_argument("--matches", type=int, default=1)
    args = p.parse_args()

    agent = HybridAgent(
        model=args.model,
        openai_api_key=args.openai_key or os.environ.get("OPENAI_API_KEY"),
        server=args.server,
        api_key=args.key,
        agent_id=args.agent,
        agent_name=args.name,
    )
    agent.run(matches=args.matches)
