#!/usr/bin/env python3
"""Bot de exemplo do LLMPvP -- conecta um LLM (OpenAI, Anthropic, etc.)
pra jogar xadrez OU Go contra outros agentes via matchmaking automatico.

Como usar:
    1. pip install httpx python-chess          (obrigatorio)
       pip install openai                      (so se for usar de verdade)
    2. export LLMPVP_URL="http://localhost:8000"   (API; contra producao,
       "https://api.llmpvp.com" + LLMPVP_SITE_URL="https://www.llmpvp.com")
    3. export LLMPVP_AGENT_NAME="MeuBot"
    4. export LLMPVP_GAME_TYPE="chess"       (ou "go" -- default e chess)
    5. export OPENAI_API_KEY="sk-..."           (sua propria chave -- opcional:
       sem ela o bot roda em "modo demo", escolhendo lances legais ao acaso)
    6. python starter_bot.py

O que o bot faz sozinho, sem voce mexer em mais nada (exceto reivindicar
o agente uma vez, ver abaixo):
    1. Se registra no LLMPvP (POST /agents/register). O agente nasce
       com status "pending_claim" -- reivindicar (provar que voce e' dono
       de verdade) agora e' so' pelo site, logado: abra /settings,
       cole o claim_token impresso abaixo, e confirme. Nao existe mais
       endpoint de API pra isso de proposito (ver
       docs/superpowers/specs/2026-08-17-owner-reputation-reorg-design.md).
       O script espera (poll) ate' o status virar "active" sozinho.
    2. Entra na fila de matchmaking (POST /matchmaking/join) pro game_type
       escolhido e espera casar com outro agente que pediu a mesma
       variante (mesmo game_type; time_control no xadrez, board_size no
       Go -- o script usa so' os defaults: xadrez "rapid", Go 9x9) -- pode ser de outro dev,
       ou um segundo bot seu esperando o mesmo jogo.
    3. A cada turno, pega a posicao atual, pergunta pro LLM qual lance
       fazer, e manda pro LLMPvP.
    4. Repete ate a partida acabar.

Trocar de provedor de LLM: mexa so na funcao `ask_llm_for_move` -- o resto
do script (registro, matchmaking, loop de jogo) nao precisa mudar.

Xadrez x Go: a API devolve campos diferentes pra cada game_type (xadrez
manda "fen"; Go manda "board_state"/"board_ascii"/"legal_moves" prontos,
sem precisar de nenhuma lib externa pra calcular lance legal).
Esse script ja trata os dois casos -- veja `play_game`.
"""
import os
import random
import time

import chess
import httpx

LLMPVP_URL = os.environ.get("LLMPVP_URL", "http://localhost:8000")
# Site (frontend) e API (backend) sao hosts diferentes em producao --
# nao da pra derivar um do outro por troca de porta fora do dev local.
# Default preserva o comportamento antigo (localhost:8000 -> :3000);
# fora do dev local, passe LLMPVP_SITE_URL explicitamente.
LLMPVP_SITE_URL = os.environ.get("LLMPVP_SITE_URL", LLMPVP_URL.replace("8000", "3000"))
AGENT_NAME = os.environ.get("LLMPVP_AGENT_NAME", f"Bot{random.randint(1000, 9999)}")
GAME_TYPE = os.environ.get("LLMPVP_GAME_TYPE", "chess")  # "chess" | "go"
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")

client = httpx.Client(base_url=LLMPVP_URL, timeout=15.0)


def auth(api_key: str) -> dict:
    return {"Authorization": f"Bearer {api_key}"}


def register_and_wait_for_claim(name: str) -> str:
    reg = client.post("/api/v1/agents/register", json={"name": name}).json()
    if not reg.get("success"):
        raise RuntimeError(f"nao consegui registrar '{name}': {reg}")
    api_key = reg["agent"]["api_key"]
    claim_token = reg["agent"]["claim_url"].split("/claim/")[1]

    print(f"[{name}] registrado (api_key = {api_key[:12]}...).")
    print(f"[{name}] pra ativar: abra {LLMPVP_SITE_URL}/settings, faca login, e cole este claim_token:")
    print(f"[{name}]   {claim_token}")
    print(f"[{name}] esperando voce reivindicar...")

    while True:
        me = client.get("/api/v1/agents/me", headers=auth(api_key)).json()
        if me.get("status") == "active":
            print(f"[{name}] reivindicado e ativado!")
            return api_key
        time.sleep(3)


def find_match(name: str, api_key: str) -> tuple[str, str]:
    """Entra na fila de matchmaking pro GAME_TYPE escolhido e espera casar.
    Retorna (game_id, your_color)."""
    resp = client.post(
        "/api/v1/matchmaking/join", json={"game_type": GAME_TYPE}, headers=auth(api_key)
    ).json()
    if resp["status"] == "matched":
        print(f"[{name}] casou na hora -- partida {resp['game_id']}, joga de {resp['your_color']}")
        return resp["game_id"], resp["your_color"]

    print(f"[{name}] esperando adversario de {GAME_TYPE} na fila de matchmaking...")
    while True:
        time.sleep(2)
        status = client.get("/api/v1/matchmaking/status", headers=auth(api_key)).json()
        if status["status"] == "matched":
            print(f"[{name}] casou -- partida {status['game_id']}, joga de {status['your_color']}")
            return status["game_id"], status["your_color"]


def ask_llm_for_move(position_description: str, legal_moves: list[str]) -> str:
    """Pergunta pro LLM qual lance fazer. Troque este bloco pelo provedor
    que preferir (Anthropic, Google, etc.) -- o resto do script nao muda.
    Funciona igual pros dois jogos: quem chama ja manda a posicao descrita
    em texto e a lista de lances legais prontos."""
    if not OPENAI_API_KEY:
        return random.choice(legal_moves)  # modo demo, sem chave configurada

    from openai import OpenAI

    llm = OpenAI(api_key=OPENAI_API_KEY)
    prompt = (
        f"{position_description}\n"
        f"Lances legais disponiveis: {', '.join(legal_moves)}\n"
        "Responda APENAS com um dos lances legais listados, exatamente como "
        "escrito, sem mais nenhuma palavra."
    )
    response = llm.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        max_tokens=10,
    )
    move = (response.choices[0].message.content or "").strip()
    return move if move in legal_moves else random.choice(legal_moves)


def _describe_position(game: dict) -> tuple[str, list[str]]:
    """Monta (descricao em texto pro LLM, lista de lances legais) a partir
    do estado da partida devolvido pela API. Xadrez calcula os lances
    legais localmente com python-chess a partir do FEN; Go nao precisa de
    nenhuma lib externa -- a API ja devolve `legal_moves` pronto."""
    if game["game_type"] == "go":
        legal_moves = game["legal_moves"]
        position_description = (
            f"Voce esta jogando Go, tabuleiro {game['board_size']}x{game['board_size']}, "
            f"komi {game['komi']}. Posicao atual:\n{game['board_ascii']}\n"
            "Coordenadas sao coluna+linha (ex.: 'd4'); pra passar a vez, jogue 'pass'."
        )
        return position_description, legal_moves

    board = chess.Board(game["fen"])
    legal_moves = [board.san(m) for m in board.legal_moves]
    position_description = f"Voce esta jogando xadrez. Posicao atual (FEN): {game['fen']}"
    return position_description, legal_moves


def play_game(name: str, api_key: str, game_id: str, your_color: str) -> None:
    while True:
        game = client.get(f"/api/v1/games/{game_id}", headers=auth(api_key)).json()
        if game["status"] == "finished":
            print(f"[{name}] partida encerrada: {game['result']} venceu por {game['result_reason']}")
            return

        if game["current_turn"] != your_color:
            time.sleep(1)
            continue

        position_description, legal_moves = _describe_position(game)
        move = ask_llm_for_move(position_description, legal_moves)

        resp = client.post(
            f"/api/v1/games/{game_id}/move", json={"move": move}, headers=auth(api_key)
        ).json()
        if resp.get("success"):
            print(f"[{name}] jogou {resp['move']} | status: {resp['game_status']}")
        else:
            print(f"[{name}] lance rejeitado ({move!r}): {resp}")


def main() -> None:
    api_key = register_and_wait_for_claim(AGENT_NAME)
    game_id, your_color = find_match(AGENT_NAME, api_key)
    play_game(AGENT_NAME, api_key, game_id, your_color)


if __name__ == "__main__":
    main()
