#!/usr/bin/env python3
"""
Agent bus — lightweight Supabase writes so the org dashboards stay live.

Ports the subset of sovereign-org-engine/core/supabase_bus.py that the
Telegram bridge needs: task ledger (agent_tasks), decision log
(agent_decisions), reports (agent_reports), and the reads behind /pulse
and /update (revenue_tracking, agent_registry, agent_escalations).
Same tables, same Supabase project (mkbnyejnzstqgyvqoqau), so
com.sovereign.dashboard and content-status keep reading fresh rows.

Differences from the org engine, by design:
  - stdlib urllib instead of httpx (chairman-agent style, zero deps)
  - every public function is FAIL-SOFT: on any error it logs a warning
    and returns None/[]/{} — the Telegram poller must never die because
    Supabase is unreachable.
"""
import json
import logging
import os
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timezone

log = logging.getLogger("chairman-telegram")

TIMEOUT = 15


def _base():
    # content-os .env stores the URL with a /rest/v1/ suffix; org-engine's
    # does not. Normalize like agent.py does.
    return os.environ.get("SUPABASE_URL", "").split("/rest/")[0].rstrip("/")


def _headers():
    # The shared anon key gets 401 on agent_* table INSERTs since the RLS
    # lockdown (verified 2026-07-04 — org-engine's own writes use the same
    # anon key, so its ledger writes were already failing silently). Prefer
    # the service key, which chairman's load_env() already provides from
    # content-os/config/.env; fall back to anon (reads still work there).
    key = (os.environ.get("SUPABASE_SERVICE_KEY")
           or os.environ.get("SUPABASE_ANON_KEY", ""))
    return {
        "apikey": key,
        "Authorization": f"Bearer {key}",
        "Content-Type": "application/json",
        "Prefer": "return=representation",
    }


def _request(method, table, params=None, payload=None):
    base = _base()
    if not base:
        raise RuntimeError("SUPABASE_URL not set")
    url = f"{base}/rest/v1/{table}"
    if params:
        qs = "&".join(f"{k}={urllib.parse.quote(str(v), safe='.,()*')}"
                      for k, v in params.items())
        url = f"{url}?{qs}"
    data = json.dumps(payload).encode() if payload is not None else None
    req = urllib.request.Request(url, data=data, headers=_headers(), method=method)
    with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
        body = r.read()
    return json.loads(body) if body else None


def _get(table, params=None):
    return _request("GET", table, params=params)


def _post(table, payload):
    return _request("POST", table, payload=payload)


def _patch(table, match, payload):
    params = {k: f"eq.{v}" for k, v in match.items()}
    return _request("PATCH", table, params=params, payload=payload)


def _soft(fn, *args, empty=None, **kwargs):
    try:
        return fn(*args, **kwargs)
    except Exception as e:
        log.warning("agent_bus fail-soft: %s(%s): %s",
                    fn.__name__, args[0] if args else "", e)
        return empty


# --- Task ledger (agent_tasks) ---

def create_task(assigned_to, assigned_by, title, description=None,
                priority="ROUTINE"):
    """Returns the new task id, or None."""
    rows = _soft(_post, "agent_tasks", {
        "assigned_to": assigned_to,
        "assigned_by": assigned_by,
        "title": title,
        "description": description,
        "priority": priority,
    })
    if isinstance(rows, list) and rows:
        return rows[0].get("id")
    return None


def complete_task(task_id, output):
    if not task_id:
        return None
    return _soft(_patch, "agent_tasks", {"id": task_id}, {
        "status": "completed",
        "output": (output or "")[:2000],
        "completed_at": datetime.now(timezone.utc).isoformat(),
    })


def fail_task(task_id, error):
    if not task_id:
        return None
    return _soft(_patch, "agent_tasks", {"id": task_id}, {
        "status": "failed",
        "output": str(error)[:500],
    })


# --- Decision log (agent_decisions) ---

def log_decision(agent_id, decision_type, context, decision, reasoning=None):
    return _soft(_post, "agent_decisions", {
        "agent_id": agent_id,
        "decision_type": decision_type,
        "context": (context or "")[:200],
        "decision": (decision or "")[:500],
        "reasoning": reasoning,
    })


# --- Reports (agent_reports) ---

def submit_report(agent_id, report_type, status="GREEN", notes=None,
                  metrics=None):
    return _soft(_post, "agent_reports", {
        "agent_id": agent_id,
        "report_type": report_type,
        "status": status,
        "completed_items": json.dumps([]),
        "in_progress_items": json.dumps([]),
        "blocked_items": json.dumps([]),
        "metrics": json.dumps(metrics or {}),
        "tomorrow_priorities": json.dumps([]),
        "notes": (notes or "")[:2000],
    })


# --- Reads behind /pulse and /update (all fail-soft to []) ---

def get_revenue_streams():
    return _soft(_get, "revenue_tracking",
                 {"select": "*", "order": "stream_name.asc"}, empty=[]) or []


def get_tasks_snapshot(limit=500):
    return _soft(_get, "agent_tasks",
                 {"select": "id,assigned_to,status", "limit": str(limit)},
                 empty=[]) or []


def get_latest_report():
    rows = _soft(_get, "agent_reports",
                 {"select": "status,created_at",
                  "order": "created_at.desc", "limit": "1"}, empty=[]) or []
    return rows[0] if rows else None


def get_system_status():
    """org-engine get_system_status(), fail-soft."""
    agents = _soft(_get, "agent_registry", {"order": "tier.asc"}, empty=[]) or []
    open_tasks = _soft(_get, "agent_tasks", {
        "status": "in.(queued,in_progress,review)",
        "select": "id,assigned_to,title,status,priority",
    }, empty=[]) or []
    escalations = _soft(_get, "agent_escalations", {
        "status": "eq.open", "order": "level.desc,created_at.asc",
    }, empty=[]) or []
    reports = _soft(_get, "agent_reports", {
        "report_type": "eq.daily", "order": "created_at.desc", "limit": "14",
    }, empty=[]) or []
    return {
        "agents": len(agents),
        "agents_active": sum(1 for a in agents if a.get("status") == "active"),
        "open_tasks": len(open_tasks),
        "open_escalations": len(escalations),
        "latest_reports": len(reports),
        "status": "OPERATIONAL" if not escalations else "ESCALATION_ACTIVE",
    }
