"""Main CLI entry point for Agent Duel."""

import os

import click
from rich.console import Console
from rich.table import Table

from . import __version__
from .agent_loader import AgentLoadError, load_agent, get_agent_game
from .api import APIClient, APIError
from .config import (
    clear_active_agent,
    clear_token,
    get_active_agent,
    get_token,
    set_active_agent,
    set_token,
)
from .match_client import play_match
from .pvp_client import play_pvp_match

console = Console()


def get_agent_path(agent_option: str | None) -> str | None:
    """Get agent path from option or active agent config."""
    if agent_option:
        return agent_option
    return get_active_agent()


def require_agent(agent_option: str | None) -> str:
    """Get agent path, raising error if not available."""
    path = get_agent_path(agent_option)
    if not path:
        console.print("[red]No active agent set.[/red]")
        console.print("Run 'agentduel activate path/to/agent.py' first.")
        raise SystemExit(1)
    if not os.path.exists(path):
        console.print(f"[red]Agent file not found: {path}[/red]")
        console.print("Run 'agentduel activate path/to/agent.py' to set a new agent.")
        raise SystemExit(1)
    return path


@click.group()
@click.version_option(version=__version__)
def cli():
    """Agent Duel CLI - Compete with AI agents in language-based games."""
    pass


@cli.command()
def login():
    """Log in to your Agent Duel account."""
    email = click.prompt("Email")
    password = click.prompt("Password", hide_input=True)

    try:
        client = APIClient()
        result = client.login(email, password)
        token = result.get("token")

        if token:
            set_token(token)
            user = result.get("user", {})
            console.print(f"[green]✓[/green] Logged in as {user.get('email', email)}")
        else:
            console.print("[red]Login failed: No token received[/red]")

    except APIError as e:
        console.print(f"[red]Login failed: {e.message}[/red]")
    except Exception as e:
        console.print(f"[red]Error: {e}[/red]")


@cli.command()
def logout():
    """Log out of your Agent Duel account."""
    clear_token()
    console.print("[green]✓[/green] Logged out")


@cli.command()
@click.argument("path", type=click.Path(exists=True))
def activate(path):
    """Set the active agent file to use for matches.

    PATH is the path to your agent Python file.
    """
    # Convert to absolute path
    abs_path = os.path.abspath(path)
    set_active_agent(abs_path)
    console.print(f"[green]✓[/green] Active agent set to: {abs_path}")


@cli.command()
def status():
    """Show your agent's status and stats."""
    token = get_token()
    if not token:
        console.print("[red]Not logged in. Run 'agentduel login' first.[/red]")
        return

    try:
        client = APIClient(token)

        # Get user info
        user = client.get_me()
        console.print()
        console.print(f"[bold]Logged in as:[/bold] {user.get('email', 'Unknown')}")

        # Show active agent file and game type
        active_agent = get_active_agent()
        if active_agent:
            if os.path.exists(active_agent):
                console.print(f"[bold]Active Agent File:[/bold] {active_agent}")
                # Show agent's GAME property if set
                agent_game = get_agent_game(active_agent)
                if agent_game:
                    game_display = "Liar's Dice" if agent_game == "liars-dice" else "Split or Steal"
                    console.print(f"[bold]Agent Game:[/bold] [cyan]{game_display}[/cyan] ({agent_game})")
                else:
                    console.print("[bold]Agent Game:[/bold] [dim]Not specified (no GAME property)[/dim]")
            else:
                console.print(f"[bold]Active Agent File:[/bold] [red]{active_agent} (not found)[/red]")
        else:
            console.print("[bold]Active Agent File:[/bold] [dim]Not set[/dim]")

        stats = client.get_agent_stats()

        console.print()
        console.print(f"[bold]Agent Name:[/bold] {stats.get('name', 'Unknown')}")
        console.print()

        table = Table(show_header=False, box=None)
        table.add_column(style="dim")
        table.add_column()

        table.add_row("Matches Played", str(stats.get("matches_played", 0)))
        table.add_row("Wins", f"[green]{stats.get('wins', 0)}[/green]")
        table.add_row("Losses", f"[red]{stats.get('losses', 0)}[/red]")
        table.add_row("Draws", str(stats.get("draws", 0)))

        console.print(table)
        console.print()

    except APIError as e:
        if e.status_code == 404:
            console.print()
            console.print("[yellow]You don't have an agent yet.[/yellow]")
            console.print("Create one at http://localhost:3000/my-agent")
        else:
            console.print(f"[red]Error: {e.message}[/red]")
    except Exception as e:
        console.print(f"[red]Error: {e}[/red]")


@cli.command()
def matches():
    """List your recent matches."""
    token = get_token()
    if not token:
        console.print("[red]Not logged in. Run 'agentduel login' first.[/red]")
        return

    try:
        client = APIClient(token)
        result = client.get_my_matches()

        matches_list = result.get("results", result) if isinstance(result, dict) else result

        if not matches_list:
            console.print("[yellow]No matches yet.[/yellow]")
            console.print("Run 'agentduel train' to play!")
            return

        table = Table(title="Your Matches")
        table.add_column("ID", style="dim")
        table.add_column("Opponent")
        table.add_column("Game")
        table.add_column("Score")
        table.add_column("Result")
        table.add_column("Date")

        for match in matches_list[:20]:  # Limit to 20
            match_id = str(match.get("id", ""))
            opponent = match.get("agent_b_name", "Training Bot")

            game_type = match.get("game_type", "split-or-steal")
            if game_type == "all":
                game_display = "All Games"
            elif game_type == "liars-dice":
                game_display = "Liar's Dice"
            else:
                game_display = "Split/Steal"

            score_a = match.get("score_a", 0)
            score_b = match.get("score_b", 0)
            score = f"{score_a} - {score_b}"

            if match.get("is_draw"):
                result_str = "[yellow]Draw[/yellow]"
            elif score_a > score_b:
                result_str = "[green]Win[/green]"
            else:
                result_str = "[red]Loss[/red]"

            created = match.get("created_at", "")[:10]

            table.add_row(match_id, opponent, game_display, score, result_str, created)

        console.print(table)
        console.print()
        console.print("View match details: http://localhost:3000/matches/<id>")

    except APIError as e:
        if e.status_code == 404:
            console.print("[yellow]You don't have an agent yet.[/yellow]")
        else:
            console.print(f"[red]Error: {e.message}[/red]")
    except Exception as e:
        console.print(f"[red]Error: {e}[/red]")


VALID_GAMES = ["split-or-steal", "liars-dice"]


def _run_single_training_match(token: str, agent_path: str, game: str) -> bool:
    """Run a single training match for the specified game.

    Returns True if successful, False otherwise.
    """
    try:
        user_agent = load_agent(agent_path)
    except AgentLoadError as e:
        console.print(f"[red]Failed to load agent: {e}[/red]")
        return False

    # Check if user has an agent on the server
    try:
        client = APIClient(token)
        client.get_my_agent()
    except APIError as e:
        if e.status_code == 404:
            console.print("[yellow]You don't have an agent yet.[/yellow]")
            name = click.prompt("Enter a name for your agent (3-20 chars, alphanumeric/underscores)")
            try:
                client.create_agent(name)
                console.print(f"[green]✓[/green] Agent '{name}' created")
            except APIError as e2:
                console.print(f"[red]Failed to create agent: {e2.message}[/red]")
                return False
        else:
            console.print(f"[red]Error: {e.message}[/red]")
            return False

    # Play the match
    game_display = "Liar's Dice" if game == "liars-dice" else "Split or Steal"
    console.print(f"\nConnecting to match server for {game_display}...")

    def print_message(msg: str):
        console.print(msg)

    try:
        play_match(user_agent, print_message, game_id=game)
        return True
    except Exception as e:
        console.print(f"[red]Match error: {e}[/red]")
        return False


@cli.command()
@click.option("--agent", "-a", type=click.Path(exists=True), help="Path to your agent Python file (overrides active agent)")
@click.option("--game", "-g", type=click.Choice(VALID_GAMES), default=None, help="Game type to play (auto-detected from agent's GAME property if not specified)")
@click.option("--all", "play_all", is_flag=True, help="Play training matches for all game types")
def train(agent, game, play_all):
    """Play a training match against the training bot.

    If your agent has a GAME property, you don't need to specify --game.

    Examples:
        agentduel train                    # Uses agent's GAME property
        agentduel train --game split-or-steal
        agentduel train -g liars-dice
        agentduel train --all
    """
    token = get_token()
    if not token:
        console.print("[red]Not logged in. Run 'agentduel login' first.[/red]")
        return

    # Get agent path first so we can check GAME property
    agent_path = require_agent(agent)

    # Check if agent has a GAME property
    agent_game = get_agent_game(agent_path)

    # If no --game specified and not --all, try to use agent's GAME property
    if not game and not play_all:
        if agent_game:
            game = agent_game
            agent_display = "Liar's Dice" if agent_game == "liars-dice" else "Split or Steal"
            console.print(f"Using game from agent's GAME property: [cyan]{agent_display}[/cyan]")
        else:
            console.print("[red]Error: You must specify a game type.[/red]")
            console.print()
            console.print("Your agent doesn't have a GAME property, so you need to specify the game:")
            console.print("  agentduel train --game split-or-steal")
            console.print("  agentduel train --game liars-dice")
            console.print("  agentduel train --all  [dim](play both games)[/dim]")
            console.print()
            console.print("Or add GAME = 'split-or-steal' or GAME = 'liars-dice' to your Agent class.")
            raise SystemExit(1)

    if game and play_all:
        console.print("[yellow]Warning: --all overrides --game. Playing all games.[/yellow]")

    # Validate agent GAME property matches the requested game (unless --all)
    if not play_all and agent_game and agent_game != game:
        game_display = "Liar's Dice" if game == "liars-dice" else "Split or Steal"
        agent_display = "Liar's Dice" if agent_game == "liars-dice" else "Split or Steal"
        console.print(f"[red]Error: Your agent is for {agent_display} but you're trying to play {game_display}.[/red]")
        console.print("Please activate an agent for the correct game type, or use --all to play all games.")
        return

    # Load the agent
    console.print(f"Loading agent from {agent_path}...")

    try:
        user_agent = load_agent(agent_path)
        console.print("[green]✓[/green] Agent loaded successfully")
    except AgentLoadError as e:
        console.print(f"[red]Failed to load agent: {e}[/red]")
        return

    if play_all:
        # Play both games - warn if agent has GAME property
        if agent_game:
            agent_display = "Liar's Dice" if agent_game == "liars-dice" else "Split or Steal"
            console.print(f"[yellow]Warning: Your agent is for {agent_display} but --all will play all games.[/yellow]")
        console.print("\n[bold]Playing training matches for all games...[/bold]\n")
        for g in VALID_GAMES:
            game_display = "Liar's Dice" if g == "liars-dice" else "Split or Steal"
            console.print(f"\n{'='*50}")
            console.print(f"[bold cyan]{game_display}[/bold cyan]")
            console.print(f"{'='*50}")
            _run_single_training_match(token, agent_path, g)
        console.print("\n[green]✓[/green] All training matches complete!")
    else:
        _run_single_training_match(token, agent_path, game)


# PvP Challenge Commands


@cli.command("list")
def list_challenges():
    """List open challenges."""
    token = get_token()
    if not token:
        console.print("[red]Not logged in. Run 'agentduel login' first.[/red]")
        return

    try:
        client = APIClient(token)
        result = client.list_challenges()

        challenges_list = result.get("results", result) if isinstance(result, dict) else result

        if not challenges_list:
            console.print("[yellow]No open challenges.[/yellow]")
            console.print("Run 'agentduel challenge' to post your own!")
            return
            return

        table = Table(title="Open Challenges")
        table.add_column("ID", style="dim")
        table.add_column("Agent")
        table.add_column("Game")
        table.add_column("Posted")
        table.add_column("Expires")

        for challenge in challenges_list[:20]:
            challenge_id = str(challenge.get("id", ""))
            agent_name = challenge.get("agent_name", "Unknown")
            game_type = challenge.get("game_type", "split-or-steal")
            if game_type == "all":
                game_display = "All Games"
            elif game_type == "liars-dice":
                game_display = "Liar's Dice"
            else:
                game_display = "Split or Steal"
            created = challenge.get("created_at", "")[:16].replace("T", " ")
            expires = challenge.get("expires_at", "")[:16].replace("T", " ")

            table.add_row(challenge_id, agent_name, game_display, created, expires)

        console.print(table)
        console.print()
        console.print("Join a challenge: agentduel join <ID>")

    except APIError as e:
        console.print(f"[red]Error: {e.message}[/red]")
    except Exception as e:
        console.print(f"[red]Error: {e}[/red]")


@cli.command()
@click.option("--agent", "-a", type=click.Path(exists=True), help="Path to your agent Python file (overrides active agent)")
@click.option("--game", "-g", type=click.Choice(VALID_GAMES), default=None, help="Game type to play (auto-detected from agent's GAME property if not specified)")
def challenge(agent, game):
    """Post an open challenge for anyone to accept.

    If your agent has a GAME property, you don't need to specify --game.

    Examples:
        agentduel challenge                # Uses agent's GAME property
        agentduel challenge --game split-or-steal
        agentduel challenge -g liars-dice
    """
    token = get_token()
    if not token:
        console.print("[red]Not logged in. Run 'agentduel login' first.[/red]")
        return

    # Get agent path first so we can check GAME property
    agent_path = require_agent(agent)

    # Check if agent has a GAME property
    agent_game = get_agent_game(agent_path)

    # If no --game specified, try to use agent's GAME property
    if not game:
        if agent_game:
            game = agent_game
            agent_display = "Liar's Dice" if agent_game == "liars-dice" else "Split or Steal"
            console.print(f"Using game from agent's GAME property: [cyan]{agent_display}[/cyan]")
        else:
            console.print("[red]Error: You must specify a game type.[/red]")
            console.print()
            console.print("Your agent doesn't have a GAME property, so you need to specify the game:")
            console.print("  agentduel challenge --game split-or-steal")
            console.print("  agentduel challenge --game liars-dice")
            console.print()
            console.print("Or add GAME = 'split-or-steal' or GAME = 'liars-dice' to your Agent class.")
            raise SystemExit(1)

    # Validate agent GAME property matches the requested game (if both specified)
    if agent_game and agent_game != game:
        game_display = "Liar's Dice" if game == "liars-dice" else "Split or Steal"
        agent_display = "Liar's Dice" if agent_game == "liars-dice" else "Split or Steal"
        console.print(f"[red]Error: Your agent is for {agent_display} but you're trying to play {game_display}.[/red]")
        console.print("Please activate an agent for the correct game type.")
        return

    # Load the agent to verify it works
    console.print(f"Loading agent from {agent_path}...")
    try:
        user_agent = load_agent(agent_path)
        console.print("[green]✓[/green] Agent loaded successfully")
    except AgentLoadError as e:
        console.print(f"[red]Failed to load agent: {e}[/red]")
        return

    game_display = "Liar's Dice" if game == "liars-dice" else "Split or Steal"

    # Check if we already have an open challenge, otherwise create one
    try:
        client = APIClient(token)

        # Check for existing open challenge for this game type
        my_challenges = client.get_my_challenges()
        my_agent = client.get_my_agent()
        my_agent_name = my_agent.get("name")

        existing_challenge = None
        for c in my_challenges:
            if c.get("agent_name") == my_agent_name and c.get("status") == "open" and c.get("game_type") == game:
                existing_challenge = c
                break

        if existing_challenge:
            challenge_id = existing_challenge.get("id")
            console.print(f"\n[green]✓[/green] Found your open {game_display} challenge (ID: {challenge_id})")
        else:
            result = client.create_challenge(game_type=game)
            challenge_id = result.get("id")
            console.print(f"\n[green]✓[/green] {game_display} challenge posted! (ID: {challenge_id})")

        console.print("Waiting for someone to join...")
        console.print("Press Ctrl+C to cancel and run 'agentduel cancel' to withdraw.\n")

        # Poll for acceptance
        import time
        while True:
            time.sleep(3)
            challenges = client.get_my_challenges()
            for c in challenges:
                if c.get("id") == challenge_id:
                    if c.get("status") == "accepted":
                        match_id = c.get("match")
                        opponent = c.get("accepted_by_name", "Unknown")
                        console.print(f"\n[green]Challenge accepted by {opponent}![/green]")
                        console.print("Starting match...\n")

                        # Play the match
                        def print_message(msg: str):
                            console.print(msg)

                        try:
                            play_pvp_match(user_agent, match_id, print_message, game_id=game)
                        except Exception as e:
                            console.print(f"[red]Match error: {e}[/red]")
                        return
                    elif c.get("status") in ("expired", "cancelled"):
                        console.print(f"\n[yellow]Challenge {c.get('status')}.[/yellow]")
                        return
                    break
            else:
                console.print("[yellow]Challenge no longer found.[/yellow]")
                return

    except KeyboardInterrupt:
        console.print("\n[yellow]Cancelled. Run 'agentduel cancel' to withdraw your challenge.[/yellow]")
    except APIError as e:
        console.print(f"[red]Error: {e.message}[/red]")
    except Exception as e:
        console.print(f"[red]Error: {e}[/red]")


@cli.command()
@click.argument("challenge_id", required=False, type=int)
@click.option("--any", "join_any", is_flag=True, help="Join any available challenge matching your agent's game")
@click.option("--agent", "-a", type=click.Path(exists=True), help="Path to your agent Python file (overrides active agent)")
def join(challenge_id, join_any, agent):
    """Join an open challenge and play the match.

    CHALLENGE_ID is the ID of the challenge to join.
    Use --any to join the first available challenge. If your agent has a GAME
    property, only challenges for that game will be considered.

    Examples:
        agentduel join 123
        agentduel join --any
    """
    token = get_token()
    if not token:
        console.print("[red]Not logged in. Run 'agentduel login' first.[/red]")
        return

    if not challenge_id and not join_any:
        console.print("[red]Specify a challenge ID or use --any to join any challenge.[/red]")
        return

    # Get agent path (from option or active agent)
    agent_path = require_agent(agent)

    # Check if agent has a GAME property for filtering
    agent_game = get_agent_game(agent_path)

    # Load the agent
    console.print(f"Loading agent from {agent_path}...")
    try:
        user_agent = load_agent(agent_path)
        if agent_game:
            agent_display = "Liar's Dice" if agent_game == "liars-dice" else "Split or Steal"
            console.print(f"[green]✓[/green] Agent loaded successfully (game: {agent_display})")
        else:
            console.print("[green]✓[/green] Agent loaded successfully")
    except AgentLoadError as e:
        console.print(f"[red]Failed to load agent: {e}[/red]")
        return

    try:
        client = APIClient(token)

        # Determine game_type based on mode
        game_type = None
        if join_any:
            result = client.list_challenges()
            challenges_list = result.get("results", result) if isinstance(result, dict) else result

            if not challenges_list:
                console.print("[yellow]No open challenges available.[/yellow]")
                return

            # Get my agent to filter out own challenges
            my_agent = client.get_my_agent()
            my_agent_name = my_agent.get("name")

            # Find first challenge matching the agent's game type (if specified)
            # "all" challenges require agents WITHOUT a GAME property (multi-game agents)
            found_challenge = None
            for c in challenges_list:
                if c.get("agent_name") != my_agent_name:
                    challenge_game = c.get("game_type", "split-or-steal")
                    # "all" challenges require agent WITHOUT GAME property
                    # Single-game challenges require matching GAME property (or no property)
                    if challenge_game == "all":
                        if agent_game is None:  # Multi-game agent can join "all"
                            found_challenge = c
                            break
                    elif agent_game is None or challenge_game == agent_game:
                        found_challenge = c
                        break

            if not found_challenge:
                if agent_game:
                    agent_display = "Liar's Dice" if agent_game == "liars-dice" else "Split or Steal"
                    console.print(f"[yellow]No {agent_display} challenges from other agents available.[/yellow]")
                    console.print(f"Your agent has GAME = '{agent_game}', so only matching challenges are shown.")
                    console.print("[dim]Note: 'All Games' challenges require agents WITHOUT a GAME property.[/dim]")
                else:
                    console.print("[yellow]No challenges from other agents available.[/yellow]")
                return

            challenge_id = found_challenge.get("id")
            game_type = found_challenge.get("game_type", "split-or-steal")
        else:
            # Get game_type from specific challenge
            result = client.list_challenges()
            challenges_list = result.get("results", result) if isinstance(result, dict) else result
            for c in challenges_list:
                if c.get("id") == challenge_id:
                    game_type = c.get("game_type", "split-or-steal")
                    break

        # Validate agent GAME property matches the game we're about to play
        # "all" challenges REQUIRE agents WITHOUT a GAME property
        if game_type == "all" and agent_game is not None:
            agent_display = "Liar's Dice" if agent_game == "liars-dice" else "Split or Steal"
            console.print(f"[red]Error: 'All Games' challenges require a multi-game agent.[/red]")
            console.print(f"Your agent has GAME = '{agent_game}' ({agent_display}).")
            console.print("To join 'All Games' challenges, remove the GAME property from your agent.")
            return

        # Single-game challenges require matching GAME property
        if agent_game and game_type and game_type != "all" and agent_game != game_type:
            game_display = "Liar's Dice" if game_type == "liars-dice" else "Split or Steal"
            agent_display = "Liar's Dice" if agent_game == "liars-dice" else "Split or Steal"
            console.print(f"[red]Error: Your agent is for {agent_display} but the challenge is for {game_display}.[/red]")
            console.print("Please activate an agent for the correct game type.")
            return

        # Join the challenge (accepts if still open, or gets match if already accepted by us)
        # Pass preferred game if joining an "all" challenge and agent has GAME property
        preferred_game = agent_game if game_type == "all" else None
        result = client.join_challenge(challenge_id, preferred_game=preferred_game)
        match_id = result.get("match_id")
        agent_a = result.get("agent_a")
        agent_b = result.get("agent_b")
        # Get actual game type from response (may differ from challenge if "all")
        actual_game_type = result.get("game_type", game_type)

        if actual_game_type == "all":
            game_display = "All Games"
        elif actual_game_type == "liars-dice":
            game_display = "Liar's Dice"
        else:
            game_display = "Split or Steal"
        console.print(f"\n[green]✓[/green] Joined challenge!")
        console.print(f"Match: {agent_a} vs {agent_b}")
        console.print(f"Game: {game_display}")
        console.print("Starting match...\n")

        # Play the match
        def print_message(msg: str):
            console.print(msg)

        try:
            play_pvp_match(user_agent, match_id, print_message, game_id=actual_game_type)
        except Exception as e:
            console.print(f"[red]Match error: {e}[/red]")

    except APIError as e:
        console.print(f"[red]Error: {e.message}[/red]")
    except Exception as e:
        console.print(f"[red]Error: {e}[/red]")


@cli.command()
def cancel():
    """Cancel your open challenge."""
    token = get_token()
    if not token:
        console.print("[red]Not logged in. Run 'agentduel login' first.[/red]")
        return

    try:
        client = APIClient(token)
        challenges = client.get_my_challenges()

        # Find open challenge by current user
        my_agent = client.get_my_agent()
        my_agent_name = my_agent.get("name")

        open_challenge = None
        for c in challenges:
            if c.get("agent_name") == my_agent_name and c.get("status") == "open":
                open_challenge = c
                break

        if not open_challenge:
            console.print("[yellow]You don't have an open challenge to cancel.[/yellow]")
            return

        client.cancel_challenge(open_challenge.get("id"))
        console.print("[green]✓[/green] Challenge cancelled.")

    except APIError as e:
        console.print(f"[red]Error: {e.message}[/red]")
    except Exception as e:
        console.print(f"[red]Error: {e}[/red]")


if __name__ == "__main__":
    cli()
