"""wivexx_relay.py — Wivexx online relay (v1.18)

Lets the owner drive this PC assistant from wivexx.com/ai with NO inbound exposure
of the PC.  The website (admin-gated) inserts 'user' rows into the Supabase
`ai_messages` table; this agent long-polls that table over HTTPS using the
SERVICE-ROLE key (which lives ONLY on the PC, in .env), runs each command through
the assistant's normal remote pipeline, and writes the reply back as an
'assistant' row that the website receives via Supabase Realtime.

Transport is plain HTTPS + `requests` (already a dependency) — no new package, no
open port, no tunnel.  Fully fail-soft: if the env/keys are missing or Supabase is
unreachable it logs once and stays inert, never affecting the local assistant.

One-time setup is in RELAY_SETUP.md.  Switched on by RELAY_ENABLED + the env vars
SUPABASE_URL and SUPABASE_SERVICE_KEY (in the Assistant .env file).
"""
import os
import threading
import time

_logger = None
_thread = None
_started = False


def set_logger(logger) -> None:
    global _logger
    _logger = logger


def _log(level: str, msg: str, *a) -> None:
    if _logger is not None:
        getattr(_logger, level, _logger.info)(msg, *a)


def _env(*names: str) -> str:
    for n in names:
        v = os.getenv(n)
        if v:
            return v.strip()
    return ""


def available() -> bool:
    """True when the relay has everything it needs to run."""
    try:
        import requests  # noqa: F401
    except Exception:
        return False
    return bool(_env("SUPABASE_URL") and _env("SUPABASE_SERVICE_KEY", "SUPABASE_SERVICE_ROLE_KEY"))


def start(handler, cfg, logger=None) -> bool:
    """Begin polling Supabase on a daemon thread.  *handler* is a callable
    handler(text)->str (the assistant's remote pipeline).  Returns True if the
    relay actually started, False if it stayed inert (missing env / disabled)."""
    global _thread, _started
    if logger is not None:
        set_logger(logger)
    if _started:
        return True
    if not getattr(cfg, "RELAY_ENABLED", False):
        _log("info", "Relay disabled (RELAY_ENABLED=False).")
        return False

    try:
        import requests  # noqa: F401
    except Exception:
        _log("warning", "Relay needs the 'requests' package — staying inert.")
        return False

    url = _env("SUPABASE_URL").rstrip("/")
    key = _env("SUPABASE_SERVICE_KEY", "SUPABASE_SERVICE_ROLE_KEY")
    if not url or not key:
        _log("warning", "Relay: SUPABASE_URL / SUPABASE_SERVICE_KEY not set in .env "
                        "— online control is inert (see RELAY_SETUP.md).")
        return False

    poll = float(getattr(cfg, "RELAY_POLL_SECS", 2.0))
    _thread = threading.Thread(
        target=_loop, args=(handler, url, key, poll),
        daemon=True, name="wivexx-relay")
    _thread.start()
    _started = True
    _log("info", "Wivexx online relay active — drive it from wivexx.com/ai.")
    return True


def _loop(handler, url: str, key: str, poll: float) -> None:
    import requests
    rest = url + "/rest/v1/ai_messages"
    headers = {
        "apikey": key,
        "Authorization": "Bearer " + key,
        "Content-Type": "application/json",
    }
    backoff = poll
    while True:
        try:
            r = requests.get(
                rest,
                params={
                    "select": "id,content,user_id",
                    "status": "eq.pending",
                    "role": "eq.user",
                    "order": "created_at.asc",
                    "limit": "5",
                },
                headers=headers, timeout=15)
            if r.status_code != 200:
                _log("warning", "Relay poll HTTP %s: %s", r.status_code, r.text[:200])
                time.sleep(min(60.0, backoff * 2)); backoff = min(60.0, backoff * 2)
                continue
            backoff = poll
            rows = r.json() or []
            for row in rows:
                _process(requests, rest, headers, handler, row)
        except Exception as e:
            _log("warning", "Relay loop error: %s", e)
            time.sleep(min(60.0, backoff * 2)); backoff = min(60.0, backoff * 2)
            continue
        time.sleep(poll)


def _patch(requests, rest, headers, mid, fields) -> None:
    try:
        requests.patch(rest, params={"id": "eq." + str(mid)}, headers=headers,
                       json=fields, timeout=15)
    except Exception as e:
        _log("warning", "Relay patch failed: %s", e)


def _process(requests, rest, headers, handler, row) -> None:
    mid = row.get("id")
    text = (row.get("content") or "").strip()
    uid = row.get("user_id")
    if not mid or not text:
        if mid:
            _patch(requests, rest, headers, mid, {"status": "done"})
        return
    # claim it so a restart / second poll never runs it twice
    _patch(requests, rest, headers, mid, {"status": "processing"})
    try:
        reply = handler(text) or "Done, sir."
        status = "done"
    except Exception as e:
        _log("error", "Relay handler failed: %s", e)
        reply = "I encountered an error processing that, sir."
        status = "error"
    # write the assistant reply back (Realtime pushes it to the /ai page)
    try:
        requests.post(rest, headers={**headers, "Prefer": "return=minimal"},
                      json={"role": "assistant", "content": reply,
                            "user_id": uid, "status": "done"}, timeout=15)
    except Exception as e:
        _log("warning", "Relay reply post failed: %s", e)
    _patch(requests, rest, headers, mid, {"status": status})
