"""
Cardinal plugin: AI players for monop-irc.

Watches monop bot output and responds as players to keep the game moving.
Each player simply rolls on their turn, buys properties when they can
afford them, answers yes/no prompts, and places minimum auction bids.

This is completely independent of the monop state-tracking plugin.
"""

import re
import logging
import random

from cardinal.decorators import event

logger = logging.getLogger(__name__)

# The monop bot nick
BOT_NICK = "monop"
# IRC command prefix
PREFIX = "."


class MonopPlayerPlugin:
    """Autopilot players for monop-irc games."""

    def __init__(self, cardinal, config):
        self.cardinal = cardinal
        self.config = config or {}

        # Player names this plugin controls (set during game setup)
        self.controlled_players = set()
        # Which player's turn it is right now
        self.current_player = None
        # Channel the game is in
        self.channel = self.config.get("channel", "#monop")
        # Player names to register (config)
        self.player_names = self.config.get(
            "players", ["alice", "bob"]
        )
        # Track game phase
        self.setup_phase = True
        self.players_registered = 0
        self.num_players = len(self.player_names)
        # Track pending prompts
        self.pending_prompt = None
        # Auction state
        self.in_auction = False
        self.auction_current_bid = 0
        # Track debt state
        self.in_debt = False
        # Track whose turn for "bad player" errors
        self.expected_player = None
        # Buffer for multi-line card text
        self.card_separator_count = 0
        # Track buy house / sell house prompts
        self.in_house_prompt = False
        # Tax choice pending
        self.tax_choice_pending = False
        # Trade pending - who needs to confirm
        self.trade_pending_for = None

        logger.info(
            "MonopPlayerPlugin loaded: players=%s, channel=%s",
            self.player_names, self.channel
        )

    def _say(self, msg):
        """Send a message to the game channel."""
        self.cardinal.sendMsg(self.channel, f"{PREFIX}{msg}")

    def _player_say(self, player, msg):
        """Send a message as a specific player (via bot command)."""
        # In monop-irc bridge, messages are routed by IRC nick.
        # Since we're a single bot, we use the prefix format.
        # The bridge maps IRC nick -> player name.
        # We can't impersonate multiple nicks from one connection.
        # Instead, we'll just send the command and let the bridge
        # figure it out. For multi-player, we'd need multiple connections.
        self._say(msg)

    @event("irc.privmsg")
    def on_msg(self, cardinal, user, channel, message):
        """Process all messages in the game channel."""
        if channel.lower() != self.channel.lower():
            return

        sender = user.nick

        # Only react to monop bot messages
        if sender.lower() != BOT_NICK.lower():
            return

        self._handle_bot_message(message)

    def _handle_bot_message(self, msg):
        """Route a monop bot message to the appropriate handler."""
        msg = msg.strip()
        if not msg:
            return

        logger.debug("Bot says: %s", msg)

        # --- Setup Phase ---
        if self._handle_setup(msg):
            return

        # --- Turn Detection ---
        # Checkpoint line: "name (N) (cash $M) on square"
        m = re.match(
            r'^(.+?)\s+\((\d+)\)\s+\(cash\s+\$(-?\d+)\)\s+on\s+(.+)$', msg
        )
        if m:
            name = m.group(1)
            self.current_player = name
            self.expected_player = name
            self.in_debt = False
            self.pending_prompt = None
            self.in_house_prompt = False
            self.tax_choice_pending = False
            # It's this player's turn - they need to roll
            if name.lower() in {n.lower() for n in self.controlled_players}:
                self._say("roll")
            return

        # --- Doubles: roll again ---
        m = re.match(r'^(.+?) rolled doubles\.\s+Goes again$', msg)
        if m:
            name = m.group(1)
            if name.lower() in {n.lower() for n in self.controlled_players}:
                self._say("roll")
            return

        # --- Buy Prompt ---
        if msg == "Do you want to buy?":
            self.pending_prompt = "buy"
            # Buy if it's our player
            if self.current_player and self.current_player.lower() in {
                n.lower() for n in self.controlled_players
            }:
                self._say("yes")
            return

        # --- Yes/No Prompts ---
        if msg in (
            "Do you want to mortgage it?",
            "Do you want to unmortgage it?",
            "Is that ok?",
        ):
            if self.current_player and self.current_player.lower() in {
                n.lower() for n in self.controlled_players
            }:
                self._say("yes")
            return

        # --- Trade confirmation ---
        m = re.match(r'^(.+?), is the trade ok\?$', msg)
        if m:
            name = m.group(1)
            if name.lower() in {n.lower() for n in self.controlled_players}:
                self._say("yes")
            return

        # --- Debt / Forced Mortgage ---
        if msg == "How are you going to fix it up?":
            self.in_debt = True
            if self.current_player and self.current_player.lower() in {
                n.lower() for n in self.controlled_players
            }:
                # Try to mortgage something, or just resign
                self._say("mortgage")
            return

        if "that leaves you broke" in msg.lower():
            # Player is bankrupt, nothing to do
            return

        if msg == "-- You are now Solvent ---":
            self.in_debt = False
            return

        # --- Mortgage prompts ---
        if msg.startswith("Which property do you want to mortgage?"):
            # The bot will list properties; we pick the first one
            # Actually monop lists them and waits for input via getinp
            # We'll handle this when we see the property list
            self.pending_prompt = "choose_mortgage"
            return

        if msg.startswith("Which property do you want to unmortgage?"):
            self.pending_prompt = "choose_unmortgage"
            return

        if msg == "You don't have any un-mortgaged property.":
            if self.in_debt:
                # Can't mortgage, try selling houses
                self._say("sell houses")
            return

        if msg == "You don't have any houses to sell!!":
            if self.in_debt:
                # Can't sell houses either, resign to bank
                self._say("resign")
            return

        # --- Resign confirmation ---
        if msg == "Do you really want to resign?":
            if self.current_player and self.current_player.lower() in {
                n.lower() for n in self.controlled_players
            }:
                self._say("yes")
            return

        if msg.startswith("Who do you wish to resign to?"):
            if self.current_player and self.current_player.lower() in {
                n.lower() for n in self.controlled_players
            }:
                self._say("bank")
            return

        # --- Auction ---
        m = re.match(r'^So it goes up for auction', msg)
        if m:
            self.in_auction = True
            self.auction_current_bid = 0
            return

        # Auction bid prompt: "name:"
        if self.in_auction:
            m = re.match(r'^(.+?):$', msg)
            if m:
                name = m.group(1).strip()
                if name.lower() in {n.lower() for n in self.controlled_players}:
                    # Bid slightly above current or drop out randomly
                    if self.auction_current_bid == 0:
                        self._say("1")
                        self.auction_current_bid = 1
                    elif random.random() < 0.3:
                        # Drop out
                        self._say("0")
                    else:
                        bid = self.auction_current_bid + 10
                        self._say(str(bid))
                        self.auction_current_bid = bid
                return

            m = re.match(
                r'^You must bid higher than (\d+) to stay in$', msg
            )
            if m:
                self.auction_current_bid = int(m.group(1))
                return

            if msg.startswith("It goes to") or msg.startswith("Nobody seems"):
                self.in_auction = False
                return

        # --- Jail ---
        m = re.match(r'^\(This is your (\w+) turn in JAIL\)$', msg)
        if m:
            if self.current_player and self.current_player.lower() in {
                n.lower() for n in self.controlled_players
            }:
                # Try to roll doubles to get out
                self._say("roll")
            return

        if msg == "But you're not IN Jail":
            return

        # --- Tax choice ---
        m = re.match(r'^Do you wish to lose 10', msg)
        if m:
            self.tax_choice_pending = True
            if self.current_player and self.current_player.lower() in {
                n.lower() for n in self.controlled_players
            }:
                # Always pick percentage (tends to be cheaper early game)
                self._say("percentage")
            return

        # --- Command prompt (-- Command:) ---
        if msg == "-- Command:":
            # Bot is waiting for a command from current player
            if self.current_player and self.current_player.lower() in {
                n.lower() for n in self.controlled_players
            }:
                if self.in_debt:
                    self._say("mortgage")
                else:
                    self._say("roll")
            return

        # --- House buying/selling prompts ---
        if msg.startswith("How many houses do you wish to"):
            self.in_house_prompt = True
            return

        # --- Bad player error: someone tried to act out of turn ---
        m = re.match(
            r"^Illegal action: bad player \((.+?)'s turn, not (.+?)\)$", msg
        )
        if m:
            correct = m.group(1)
            self.current_player = correct
            return

        # --- Illegal response: we sent something wrong ---
        if msg.startswith("Illegal response:"):
            logger.warning("Got illegal response: %s", msg)
            return

        # --- Valid inputs list (help) ---
        if msg.startswith("Valid inputs are:"):
            # Parse valid options, pick the first reasonable one
            options_str = msg[len("Valid inputs are:"):].strip()
            options = [o.strip().strip("'\"") for o in options_str.split(",")]
            if options and self.current_player and \
                    self.current_player.lower() in {
                        n.lower() for n in self.controlled_players}:
                # Pick first non-quit option
                for opt in options:
                    if opt.lower() not in ("quit", ""):
                        self._say(opt)
                        break
            return

    def _handle_setup(self, msg):
        """Handle game setup messages. Returns True if handled."""
        if not self.setup_phase:
            return False

        if msg == "How many players?":
            self._say(str(self.num_players))
            return True

        if msg.startswith("Sorry. Number must range from"):
            self._say(str(self.num_players))
            return True

        m = re.match(r"^Player (\d+), say ''me'' please\.$", msg)
        if m:
            player_idx = int(m.group(1)) - 1
            if player_idx < len(self.player_names):
                name = self.player_names[player_idx]
                self.controlled_players.add(name)
                self._say(name)
            return True

        # Player registered and rolling
        m = re.match(r'^(.+?)\s+\((\d+)\)\s+rolls\s+(\d+)$', msg)
        if m:
            return True

        # Who goes first
        m = re.match(r'^(.+?)\s+\((\d+)\)\s+goes first$', msg)
        if m:
            self.setup_phase = False
            self.current_player = m.group(1)
            return True

        # Re-roll for ties
        if "rolled the same thing" in msg:
            return True

        return False

    def close(self):
        logger.info("MonopPlayerPlugin unloaded")


entrypoint = MonopPlayerPlugin
