"""Fetch Sonilo account services and usage as a terminal dashboard.

Run:
    export SONILO_API_KEY=sk_...
    python account-usage-dashboard.py

This example calls GET /v1/account/services and GET /v1/account/usage. It is
safe for integration smoke tests because it does not start generation jobs. It
classifies 401 as auth_invalid, 403 as forbidden, 402 as insufficient_balance,
404 as not_found, and respects Retry-After for 429.
"""

import json
import os
import sys
import time

import requests

API_BASE = "https://api.sonilo.com/v1"
USER_AGENT = "SoniloPythonExample/1.0 (+https://platform.sonilo.com/docs)"


def retry_after_seconds(resp, attempt):
    value = resp.headers.get("Retry-After")
    if value:
        try:
            return max(1, int(value))
        except ValueError:
            return 5
    return min(2**attempt, 30)


def rate_limit_reason(resp):
    """The API's own 429 sentence, which names the limit that was hit.

    Two limits share this status — requests per minute and concurrent
    generations — and only the message tells them apart, so a client that
    discards it cannot tell whether to slow down or to wait for a slot.
    """
    try:
        return str(resp.json().get("message") or "").strip()
    except ValueError:
        return ""


def classify_error(resp):
    body = resp.text
    code = f"http_{resp.status_code}"
    message = body
    try:
        payload = resp.json()
        code = payload.get("code", code)
        message = payload.get("message", body)
    except ValueError:
        pass
    if resp.status_code == 401:
        return "auth_invalid", (
            "auth_invalid: set a real SONILO_API_KEY and send "
            "Authorization: Bearer <key>. Do not retry until the key is fixed."
        )
    if resp.status_code == 403:
        return "forbidden", (
            "forbidden: the API key was understood, but this account cannot "
            "access account usage for this workspace."
        )
    if resp.status_code == 402 and code == "payment_required":
        return "insufficient_balance", (
            "insufficient_balance: add balance or use an eval key with enough "
            f"credits. API returned payment_required: {message}"
        )
    if resp.status_code == 404:
        return "not_found", (
            "not_found: check that API_BASE is https://api.sonilo.com/v1, not a "
            f"documentation URL or local proxy route. API returned: {message}"
        )
    return code, f"Sonilo request failed with HTTP {resp.status_code}: {message}"


def get_json(path, headers):
    url = f"{API_BASE}{path}"
    reason = ""
    for attempt in range(1, 6):
        resp = requests.get(url, headers=headers, timeout=30)
        if resp.status_code == 429:
            reason = rate_limit_reason(resp)
            delay = retry_after_seconds(resp, attempt)
            resp.close()
            print(f"Rate limited: {reason} Retrying after {delay}s...", file=sys.stderr)
            time.sleep(delay)
            continue
        if not resp.ok:
            _, message = classify_error(resp)
            raise RuntimeError(message)
        return resp.json()
    raise RuntimeError(
        f"rate_limit_exceeded: still receiving 429 after retries. {reason}".strip()
    )


def main():
    api_key = os.environ.get("SONILO_API_KEY")
    if not api_key:
        raise RuntimeError("Set SONILO_API_KEY before running this script")

    headers = {
        "Authorization": f"Bearer {api_key}",
        "User-Agent": USER_AGENT,
    }
    services = get_json("/account/services", headers)  # GET /v1/account/services
    usage = get_json("/account/usage?days=30", headers)  # GET /v1/account/usage

    print("# Sonilo account usage")
    print()
    print("## Services")
    print(json.dumps(services, indent=2, sort_keys=True))
    print()
    print("## Usage")
    print(json.dumps(usage, indent=2, sort_keys=True))


if __name__ == "__main__":
    try:
        main()
    except Exception as exc:
        print(f"Error: {exc}", file=sys.stderr)
        sys.exit(1)
