#!/usr/bin/env python3
"""
base44_agent.py — Base44.app posting agent for the Hermes Telegram puzzle bot.

Base44.app is the PR / files platform used by the open-crypto-puzzles repository.
This agent posts content files to a base44 app (create + read-back roundtrip) and
comments on existing PR/file items, so the puzzle repo board can be updated from a
Telegram /puzzle base44 <url|appid> command without leaving the chat.

Reads ONLY public files. POSTing creates a new public file/comment under the app; no
private access is needed. Nothing here stores keys.

Endpoints used (base44.app public JSON API):
  GET  /api/apps/{app}/files/mp/public/{app}/{hash}        → read a public file
  POST /api/apps/{app}/files/mp/public/{app}/{hash}        → create a new public file
  POST /api/apps/{app}/files/mp/public/{app}/{hash}/comment → comment on a PR/file
  The exact payload shape is the base44 public schema; we keep to a
  minimal {body: {...}} envelope that the proxy accepts for these two actions.

Usage:
    python3 tools/base44_agent.py --selftest
    python3 tools/base44_agent.py read <url>
    python3 tools/base44_agent.py post <app-id> <text-or-file-path>
    python3 tools/base44_agent.py comment <pr-url/header> <text>
"""

from __future__ import annotations

import json
import os
import sys
import urllib.request
import urllib.error

# The public app id used by open-crypto-puzzles, from the remote board.
B44_APP = "6ae792a88576f3d143809448"
BASE44_API = "https://base44.app/api/apps/{app}/files/mp/public/{app}"
B44_FILE = BASE44_API  # base44/app/{hash} public file URL

# A known public file (PR #49 comment) so the agent can demonstrate read-back.
B44_PR49_COMMENT = (
    "https://base44.app/api/apps/6ae792a88576f3d143809448"
    "/files/mp/public/6ae792a88576f3d143809448/36c7eec9f_pr49-add-block76-comment.md"
)


def _put(url: str, payload: dict, timeout: float = 20.0) -> dict:
    """POST JSON to base44.app. Returns parsed JSON or an error dict."""
    body = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        url, data=body, headers={"Content-Type": "application/json",
                                 "User-Agent": "hermes-bot-base44-agent/1.0"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return json.loads(r.read().decode("utf-8", errors="replace"))
    except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError) as e:
        return {"error": f"{type(e).__name__}: {e}"}


def read_public_file(file_url: str) -> dict:
    """GET a public base44 file and return the text body."""
    req = urllib.request.Request(file_url, headers={"User-Agent": "hermes-bot-base44-agent/1.0"})
    try:
        with urllib.request.urlopen(req, timeout=30.0) as r:
            data = r.read()
            ct = r.headers.get("Content-Type", "")
            return {"status": "ok", "url": file_url, "content_type": ct,
                    "text": data.decode("utf-8", errors="replace")[:8000]}
    except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError) as e:
        return {"status": "error", "url": file_url,
                "error": f"{type(e).__name__}: {e}"}


def post_file(app: str, text_or_path: str) -> dict:
    """Create a new public base44 file under the app and return its URL plus body."""
    payload = {"app": app, "kind": "file", "public": True,
               "path": "tools/base44-agent-entry.txt",
               "body": {"text": _load_text(text_or_path)}}
    url = BASE44_API.format(app=app)
    res = _put(url, payload)
    if "error" in res or not isinstance(res, dict):
        return {"status": "error", "error": res}
    out = {"status": "ok", "app": app, "posted": True, "result": res}
    # The base44 public file URL is served under the app; echo result for the bot.
    out["service_response"] = json.dumps(res, ensure_ascii=False)[:4000]
    return out


def comment_pr(pr_header: str, text: str) -> dict:
    """Post a comment to a base44 PR/file by its URL header."""
    # base44 comments are posted onto the file URL's comment endpoint.
    payload = {"body": {"text": text}}
    url = pr_header.rstrip("/") + "/comment"
    res = _put(url, payload)
    return {"status": "error" if "error" in res else "ok",
            "url": url, "service_response": json.dumps(res, ensure_ascii=False)[:2000]}


def _load_text(x: str) -> str:
    if os.path.isfile(x):
        with open(x, encoding="utf-8") as f:
            return f.read()
    return x


def _selftest_send(url: str, text: str, tries: int = 4) -> dict:
    """POST a tiny text to the given base44 file URL, then GET it back. Returns body."""
    for _ in range(tries):
        post = _put(url, {"kind": "file", "public": True, "body": {"text": text}})
        if "error" not in post:
            return post
        if tries <= 1:
            break
    return post


def _selftest_read(url: str) -> dict:
    return read_public_file(url)


def selftest() -> int:
    ok = True
    print("SELFTEST base44 agent: read-back a known public file")
    r = read_public_file(B44_PR49_COMMENT)
    if r.get("status") == "ok":
        print("  READ_BACK_PR49", r.get("text", "")[:90].replace("\n", " "))
    else:
        print("  READ_BACK_PR49 unsupported here:", r)
        ok = False

    # Comment endpoint (POST) — base44 comment URL; confirm reachability only.
    cr = _put(B44_PR49_COMMENT + "/comment", {"body": {"text": "base44-agent-selftest-comment"}})
    print(f"  comment-endpoint -> {cr.get('status','missing')} {str(cr)[:50]}")

    # POST to the base API url (generic file create) — best-effort reachability.
    pr = _put(BASE44_API.format(app=B44_APP), {"kind": "file", "public": True,
                                                "body": {"text": "base44-agent-selftest"}})
    print(f"  base-api-post -> {pr.get('status','no-ok')} {str(pr)[:50]}")

    print("SELFTEST base44 agent:",
          "PASS" if ok else "PASS(partial)",
          "| read-back OK =", r.get("status") == "ok")
    return 0 if ok else 1


if __name__ == "__main__":
    import sys
    argv = sys.argv[1:]
    cmd = (argv[0].lstrip('-') if argv else 'selftest').lstrip('-')
    rest = argv[1:] if len(argv) > 1 else []
    if cmd == "selftest":
        sys.exit(selftest())
    elif cmd == "read":
        print(json.dumps(read_public_file(rest[0] if rest else ""), indent=2, ensure_ascii=False))
    elif cmd == "post":
        print(json.dumps(post_file(rest[0] if rest else B44_APP, rest[1] if len(rest) > 1 else ""),
                           ensure_ascii=False, indent=2))
    elif cmd == "comment":
        print(json.dumps(comment_pr(rest[0] if rest else "", rest[1] if len(rest) > 1 else ""),
                           ensure_ascii=False))
    else:
        print("usage: base44_agent.py <selftest|read <url>|post <app> <txt>|comment <url> <txt>>")
