"""
topgameai.cli
~~~~~~~~~~~~~

Command-line interface for the topgameai SDK.

Entry point: ``topgameai``

Sub-commands
------------
games
    List all available games on the platform.

leaderboard <game>
    Print the top-N leaderboard entries for a game.

benchmark
    Run a built-in random-action agent for quick smoke-testing.

Usage examples::

    topgameai games
    topgameai leaderboard snake
    topgameai leaderboard snake --limit 20
    topgameai benchmark --game snake --agent-name "my-bot" --steps 100
    topgameai benchmark --game snake --episodes 5 --verbose
"""

from __future__ import annotations

import argparse
import json
import random
import sys
from typing import Any, Dict, List, Optional

from topgameai.arena import Arena, ArenaConnectionError, ArenaError, ArenaHTTPError


# ---------------------------------------------------------------------------
# Built-in demo agents
# ---------------------------------------------------------------------------

_DIRECTIONS = ["up", "down", "left", "right"]


def _random_direction_agent(state: Dict[str, Any]) -> str:  # noqa: ARG001
    """Return a random cardinal direction — works for snake, pac-man, etc."""
    return random.choice(_DIRECTIONS)


def _random_dict_agent(state: Dict[str, Any]) -> str:
    """Fallback: just sends a random direction regardless of game type."""
    return _random_direction_agent(state)


# ---------------------------------------------------------------------------
# Formatting helpers
# ---------------------------------------------------------------------------

_RESET = "\033[0m"
_BOLD = "\033[1m"
_GREEN = "\033[32m"
_CYAN = "\033[36m"
_YELLOW = "\033[33m"
_RED = "\033[31m"


def _supports_color() -> bool:
    """Return True if stdout appears to support ANSI escape codes."""
    return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()


def _c(text: str, *codes: str) -> str:
    """Wrap *text* in ANSI colour codes if the terminal supports it."""
    if not _supports_color():
        return text
    return "".join(codes) + text + _RESET


def _print_table(rows: List[List[str]], headers: List[str]) -> None:
    """Print a simple aligned ASCII table."""
    all_rows = [headers] + rows
    col_widths = [
        max(len(str(row[i])) for row in all_rows)
        for i in range(len(headers))
    ]

    def _fmt_row(row: List[str], bold: bool = False) -> str:
        cells = [str(row[i]).ljust(col_widths[i]) for i in range(len(headers))]
        line = "  ".join(cells)
        return _c(line, _BOLD) if bold else line

    sep = "  ".join("-" * w for w in col_widths)
    print(_fmt_row(headers, bold=True))
    print(sep)
    for row in rows:
        print(_fmt_row(row))


# ---------------------------------------------------------------------------
# Sub-command handlers
# ---------------------------------------------------------------------------

def cmd_games(args: argparse.Namespace) -> int:
    """Handle: topgameai games"""
    arena = Arena(base_url=args.base_url)
    try:
        games = arena.games()
    except ArenaHTTPError as exc:
        print(_c(f"Error {exc.status_code}: {exc.detail}", _RED), file=sys.stderr)
        return 1
    except ArenaConnectionError as exc:
        print(_c(f"Connection error: {exc}", _RED), file=sys.stderr)
        return 1

    if not games:
        print("No games available.")
        return 0

    if args.json:
        print(json.dumps(games, indent=2))
        return 0

    rows = [
        [
            g.get("slug", ""),
            g.get("name", ""),
            g.get("description", "")[:60],
        ]
        for g in games
    ]
    _print_table(rows, ["Slug", "Name", "Description"])
    print(f"\n{_c(str(len(games)), _BOLD)} game(s) available.")
    return 0


def cmd_leaderboard(args: argparse.Namespace) -> int:
    """Handle: topgameai leaderboard <game>"""
    arena = Arena(base_url=args.base_url)
    try:
        entries = arena.leaderboard(args.game, limit=args.limit)
    except ArenaHTTPError as exc:
        print(_c(f"Error {exc.status_code}: {exc.detail}", _RED), file=sys.stderr)
        return 1
    except ArenaConnectionError as exc:
        print(_c(f"Connection error: {exc}", _RED), file=sys.stderr)
        return 1

    if not entries:
        print(f"No leaderboard data for {args.game!r}.")
        return 0

    if args.json:
        print(json.dumps(entries, indent=2))
        return 0

    print(_c(f"\nLeaderboard: {args.game}", _BOLD, _CYAN))
    print()
    rows = [
        [
            str(e.get("rank", i + 1)),
            str(e.get("agent_name", "")),
            str(e.get("score", "")),
            str(e.get("won", "")),
            str(e.get("steps", "")),
        ]
        for i, e in enumerate(entries)
    ]
    _print_table(rows, ["Rank", "Agent", "Score", "Won", "Steps"])
    return 0


def cmd_benchmark(args: argparse.Namespace) -> int:
    """Handle: topgameai benchmark --game <game> [options]"""
    arena = Arena(base_url=args.base_url)

    print(
        _c(
            f"Benchmarking '{args.game}' for {args.episodes} episode(s) "
            f"(max {args.steps} steps each)...",
            _CYAN,
        )
    )

    agent_fn = _random_direction_agent

    try:
        results = arena.benchmark(
            args.game,
            agent_fn,
            agent_name=args.agent_name,
            n_episodes=args.episodes,
            max_steps=args.steps,
            seed=args.seed,
            verbose=args.verbose,
        )
    except ArenaHTTPError as exc:
        print(_c(f"Error {exc.status_code}: {exc.detail}", _RED), file=sys.stderr)
        return 1
    except ArenaConnectionError as exc:
        print(_c(f"Connection error: {exc}", _RED), file=sys.stderr)
        return 1
    except ArenaError as exc:
        print(_c(f"Arena error: {exc}", _RED), file=sys.stderr)
        return 1

    if args.json:
        print(json.dumps(results, indent=2))
        return 0

    # Summary table
    print()
    rows = [
        [
            str(r["episode"]),
            str(r["steps"]),
            str(r["score"]) if r["score"] is not None else "-",
            str(r["won"]) if r["won"] is not None else "-",
            r["session_id"],
        ]
        for r in results
    ]
    _print_table(rows, ["Episode", "Steps", "Score", "Won", "Session ID"])

    # Aggregate stats
    scores = [r["score"] for r in results if r["score"] is not None]
    if scores:
        avg = sum(scores) / len(scores)
        best = max(scores)
        worst = min(scores)
        print()
        print(
            f"Avg score: {_c(f'{avg:.2f}', _BOLD)}  "
            f"Best: {_c(str(best), _GREEN)}  "
            f"Worst: {_c(str(worst), _YELLOW)}"
        )

    wins = sum(1 for r in results if r["won"])
    print(f"Win rate: {_c(f'{wins}/{len(results)}', _BOLD)}")
    return 0


# ---------------------------------------------------------------------------
# Argument parser
# ---------------------------------------------------------------------------

def _build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="topgameai",
        description="CLI for the AI Arena platform at topgameai.com",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  topgameai games
  topgameai leaderboard snake
  topgameai leaderboard snake --limit 20
  topgameai benchmark --game snake --agent-name "my-bot" --steps 100
  topgameai benchmark --game snake --episodes 5 --verbose
        """,
    )

    # Global flags
    parser.add_argument(
        "--base-url",
        default="https://topgameai.com/api/arena",
        metavar="URL",
        help="Override the Arena API base URL (default: %(default)s)",
    )
    parser.add_argument(
        "--json",
        action="store_true",
        help="Output raw JSON instead of formatted tables",
    )
    parser.add_argument(
        "--version",
        action="version",
        version="topgameai 1.0.0",
    )

    sub = parser.add_subparsers(dest="command", metavar="<command>")
    sub.required = True

    # -- games ----------------------------------------------------------------
    sub.add_parser(
        "games",
        help="List all games available on the platform",
        description="Fetch and display every game available on AI Arena.",
    )

    # -- leaderboard ----------------------------------------------------------
    lb_parser = sub.add_parser(
        "leaderboard",
        help="Show the leaderboard for a game",
        description="Fetch and display the top leaderboard entries for a game.",
    )
    lb_parser.add_argument("game", metavar="GAME", help="Game slug (e.g. snake)")
    lb_parser.add_argument(
        "--limit",
        type=int,
        default=10,
        metavar="N",
        help="Number of entries to show (default: %(default)s)",
    )

    # -- benchmark ------------------------------------------------------------
    bm_parser = sub.add_parser(
        "benchmark",
        help="Run a random agent against a game for smoke-testing",
        description=(
            "Play one or more episodes of a game using a built-in random "
            "agent and display the results.  Useful for verifying that the "
            "SDK can reach the platform and that the game is working."
        ),
    )
    bm_parser.add_argument(
        "--game",
        required=True,
        metavar="GAME",
        help="Game slug to benchmark (e.g. snake)",
    )
    bm_parser.add_argument(
        "--agent-name",
        default="cli-bot",
        metavar="NAME",
        help="Agent display name (default: %(default)s)",
    )
    bm_parser.add_argument(
        "--episodes",
        type=int,
        default=1,
        metavar="N",
        help="Number of episodes to play (default: %(default)s)",
    )
    bm_parser.add_argument(
        "--steps",
        type=int,
        default=100,
        metavar="N",
        help="Maximum steps per episode (default: %(default)s)",
    )
    bm_parser.add_argument(
        "--seed",
        type=int,
        default=None,
        metavar="INT",
        help="Random seed for reproducible games",
    )
    bm_parser.add_argument(
        "--verbose",
        action="store_true",
        help="Print a summary line after each episode",
    )

    return parser


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------

def main(argv: Optional[List[str]] = None) -> None:
    """Main entry point; called by the ``topgameai`` console script."""
    parser = _build_parser()
    args = parser.parse_args(argv)

    # Propagate --json to sub-commands via namespace (it is a global flag).
    handlers = {
        "games": cmd_games,
        "leaderboard": cmd_leaderboard,
        "benchmark": cmd_benchmark,
    }

    handler = handlers.get(args.command)
    if handler is None:
        parser.print_help()
        sys.exit(1)

    sys.exit(handler(args))


if __name__ == "__main__":
    main()
