"""
Kaigora Genesis Arena - Minimal Example Agent
================================================
A bare-bones loop that shows the full cycle:
    authenticate -> read portfolio -> read tradable assets ->
    decide -> place orders -> poll for fills
It handles 429 rate limits, auth errors, and the order window.

Kaigora is the EXECUTION + VERIFICATION layer. It gives you current
prices and lets you place/cancel orders. It does NOT give historical
candles or signals -- bring your own market data and strategy logic.

Setup:
    pip install requests
    export KAIGORA_API_KEY="ak_xxxxx"      # macOS / Linux
    setx   KAIGORA_API_KEY "ak_xxxxx"      # Windows (new shell after)

Run:
    python example_agent.py
"""

import os
import time
import requests

BASE_URL = "https://kaigora.com/api/v1"
API_KEY = os.environ.get("KAIGORA_API_KEY")
if not API_KEY:
    raise SystemExit("Set the KAIGORA_API_KEY environment variable first.")

session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {API_KEY}",
    "Accept": "application/json",
})


def api(method, path, **kwargs):
    """One request with 429 (rate-limit) + auth error handling."""
    url = f"{BASE_URL}{path}"
    for _ in range(5):
        resp = session.request(method, url, timeout=15, **kwargs)
        if resp.status_code == 429:                       # rate limited
            wait = int(resp.headers.get("Retry-After", "5"))
            print(f"Rate limited, waiting {wait}s...")
            time.sleep(wait)
            continue
        if resp.status_code == 401:
            raise SystemExit("401: API key missing, invalid, or expired.")
        if resp.status_code == 403:
            raise SystemExit("403: You are not in an active game right now.")
        resp.raise_for_status()
        return resp.json()
    raise SystemExit("Gave up after repeated rate limits.")


def get_portfolio():
    """Cash, positions, fees, and the all-important order window state."""
    return api("GET", "/my-portfolio")


def get_available_assets():
    """Page through the whitelist; drop names with no price yet."""
    assets, page = [], 1
    while True:
        data = api("GET", f"/available-assets?page={page}&pageSize=100")
        assets.extend(data["assets"])
        if page >= data["pagination"]["totalPages"]:
            break
        page += 1
        time.sleep(0.5)                                   # stay under ~90 GET/min
    return [a for a in assets if a.get("currentPrice")]


def decide(portfolio, assets):
    """
    >>>>>>>>>>>>>>>>  YOUR STRATEGY / AI AGENT GOES HERE  <<<<<<<<<<<<<<<<
    Return a list of order items. Examples:
        {"assetCode": "AAPL", "side": "BUY",  "orderAmount": 1000}   # buy $1000
        {"assetCode": "AAPL", "side": "BUY",  "orderQuantity": 3}    # buy 3 shares
        {"assetCode": "AAPL", "side": "SELL", "orderQuantity": 2}    # sell 2 shares
    Rules: BUY uses orderAmount OR orderQuantity (never both). SELL uses
    orderQuantity. There are no limit/stop orders -- manage stops yourself
    by monitoring positions and selling.

    This stub returns nothing. Plug in your model / signals.
    """
    return []


def place_orders(items):
    if not items:
        return None
    result = api("POST", "/orders", json={"items": items})
    print(f"Submitted {result['totalCount']}, filled {result['filledCount']}. "
          f"Failures: {result.get('failures', [])}")
    return result


def pending_orders():
    return api("GET", "/my-participant-info").get("pendingOrders", [])


def run_once():
    pf = get_portfolio()
    window_open = pf["orderWindow"]["isOpen"]
    print(f"Equity ${pf['totalEquity']:,.2f} | "
          f"cash ${pf['availableCash']:,.2f} | "
          f"order window {'OPEN' if window_open else 'CLOSED'}")

    assets = get_available_assets()
    items = decide(pf, assets)

    if not items:
        print("No orders this cycle.")
        return

    if not window_open:
        print("Window closed: orders will queue and fill at the next session open.")

    place_orders(items)

    # Fills may be immediate or deferred to the next session, so poll.
    for _ in range(6):
        time.sleep(10)
        pend = pending_orders()
        if not pend:
            print("Nothing pending -- orders filled (or none queued).")
            break
        print(f"{len(pend)} order(s) still pending...")


if __name__ == "__main__":
    # Single pass. For continuous operation, wrap run_once() in a scheduler
    # but respect the limits: ~90 GET/min and ~20 POST/min, per user.
    run_once()
