#!/usr/bin/env bash
# ============================================================================
# setup-mesin.sh — Deploy webhook Hermes + plugin http_callback di mesin baru
#
# Satu mesin = satu webhook UNIK (identitas user/mesin). Script ini:
#   1. Cek prasyarat (hermes, curl, python3)
#   2. Aktifkan webhook platform (port 8644)
#   3. Buat plugin http_callback (3 file) di $HERMES_HOME/plugins/
#   4. Enable plugin + config (endpoint_url, api_key, webhook_base)
#   5. Buat subscription webhook dengan NAMA UNIK
#   6. Restart gateway + verifikasi koneksi
#   7. Tes preflight ke backend Anda + cetak ringkasan identitas
#
# Pemakaian:
#   ./setup-mesin.sh                            # interaktif (akan ditanya)
#   ./setup-mesin.sh joko-jobseeker "https://backend-anda.com/webhook/hermes" random123
#   ./setup-mesin.sh joko-jobseeker "https://..." random123 "https://mesin.tunnel.com"   # + webhook_base
#   DRY_RUN=1 ./setup-mesin.sh joko-jobseeker "https://..." random123                     # preview saja
#
# Variabel env yang dipakai (bisa di-override):
#   HERMES_HOME (default: ${HERMES_HOME:-$HOME/.hermes})
#   HERMES_BIN  (default: hermes — cari di PATH)
# ============================================================================
set -euo pipefail

# ── Warna output ────────────────────────────────────────────────────────────
C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'; C_CYAN=$'\033[36m'; C_BOLD=$'\033[1m'; C_RESET=$'\033[0m'
ok()   { echo "${C_GREEN}✓${C_RESET} $*"; }
warn() { echo "${C_YELLOW}⚠ $*${C_RESET}"; }
fail() { echo "${C_RED}✗ $*${C_RESET}"; }
info() { echo "${C_CYAN}→ $*${C_RESET}"; }
step() { echo; echo "${C_BOLD}── $* ──${C_RESET}"; }

# ── Deteksi environment ─────────────────────────────────────────────────────
HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
HERMES_BIN="${HERMES_BIN:-hermes}"

# Cari hermes binary (PATH atau lokasi umum)
if ! command -v "$HERMES_BIN" >/dev/null 2>&1; then
  for cand in "$HOME/.local/bin/hermes" /opt/hermes/bin/hermes /usr/local/bin/hermes; do
    if [ -x "$cand" ]; then HERMES_BIN="$cand"; break; fi
  done
fi

# ── Argumen ─────────────────────────────────────────────────────────────────
NAMA_UNIK="${1:-}"
ENDPOINT_URL="${2:-}"
API_KEY="${3:-}"
WEBHOOK_BASE="${4:-}"
DRY_RUN="${DRY_RUN:-0}"

# ── Prasyarat ───────────────────────────────────────────────────────────────
step "1/7 — Cek prasyarat"
for cmd in "$HERMES_BIN" curl python3; do
  if command -v "$cmd" >/dev/null 2>&1 || [ -x "$cmd" ]; then
    ok "ditemukan: $cmd"
  else
    fail "tidak ditemukan: $cmd (butuh hermes, curl, python3)"
    exit 1
  fi
done
ok "HERMES_HOME = $HERMES_HOME"

# ── Input interaktif (kalau argumen kosong) ─────────────────────────────────
if [ -z "$NAMA_UNIK" ]; then
  echo
  info "Masukkan NAMA WEBHOOK UNIK untuk mesin ini (identitas user/mesin)."
  info "Contoh: joko-jobseeker, siti-freelance, student-chat. Hindari spasi/karakter khusus."
  read -r -p "Nama unik: " NAMA_UNIK
fi
if [ -z "$ENDPOINT_URL" ]; then
  echo
  info "Masukkan ENDPOINT CALLBACK (backend/n8n/API Anda yang menerima POST JSON)."
  read -r -p "Endpoint URL: " ENDPOINT_URL
fi
if [ -z "$API_KEY" ]; then
  read -r -p "API key untuk header X-Api-Key (kosong = tanpa auth): " API_KEY
fi

# Validasi format nama unik (a-z0-9- saja, aman untuk URL & chat_id)
if ! [[ "$NAMA_UNIK" =~ ^[a-z0-9][a-z0-9-]{1,62}$ ]]; then
  fail "Nama unik tidak valid: '$NAMA_UNIK' (pakai huruf kecil, angka, strip; 2-63 char)"
  exit 1
fi
if ! [[ "$ENDPOINT_URL" =~ ^https?:// ]]; then
  fail "Endpoint URL harus diawali http:// atau https://"
  exit 1
fi

echo
info "${C_BOLD}Ringkasan konfigurasi:${C_RESET}"
echo "  Nama unik     : $NAMA_UNIK"
echo "  Endpoint      : $ENDPOINT_URL"
echo "  API key       : ${API_KEY:-(tanpa auth)}"
echo "  Webhook base  : ${WEBHOOK_BASE:-http://localhost:8644}"
echo "  Hermes home   : $HERMES_HOME"
if [ "$DRY_RUN" = "1" ]; then
  echo
  warn "DRY-RUN mode — tidak ada perubahan yang dilakukan. Selesai."
  exit 0
fi
read -r -p "Lanjut? [y/N] " confirm
[[ "$confirm" =~ ^[yY]$ ]] || { info "Dibatalkan."; exit 0; }

# ── Aktifkan webhook platform ───────────────────────────────────────────────
step "2/7 — Aktifkan webhook platform (port 8644)"
"$HERMES_BIN" config set platforms.webhook.enabled true >/dev/null 2>&1 && ok "platforms.webhook.enabled = true"
"$HERMES_BIN" config set platforms.webhook.extra.port 8644 >/dev/null 2>&1 && ok "platforms.webhook.extra.port = 8644"
# Global HMAC secret (opsional tapi disarankan):
if [ -n "${WEBHOOK_SECRET:-}" ]; then
  "$HERMES_BIN" config set platforms.webhook.extra.secret "$WEBHOOK_SECRET" >/dev/null 2>&1 && ok "platform secret di-set"
else
  info "Tanpa WEBHOOK_SECRET global — tiap subscription tetap punya secret HMAC sendiri."
fi

# ── Buat plugin http_callback ───────────────────────────────────────────────
step "3/7 — Buat plugin http_callback"
PLUGIN_DIR="$HERMES_HOME/plugins/http_callback"
mkdir -p "$PLUGIN_DIR"

cat > "$PLUGIN_DIR/plugin.yaml" <<'YAML'
name: http-callback-platform
label: http_callback
kind: platform
version: 1.0.0
description: >-
  HTTP callback platform adapter for Hermes Agent. Posts agent replies to a
  user-defined HTTP endpoint as JSON {route, delivery_id, reply, webhook_url}
  with an X-Api-Key header — for saving agent responses into your own database
  or forwarding them to your backend. Stateless: no inbound subscription, only
  outbound POST. Uses httpx (already a Hermes dependency).
author: hermes
requires_env: []
optional_env:
  - name: HTTP_CALLBACK_ENDPOINT_URL
    description: "Endpoint URL to POST agent replies to (e.g. https://api.example.com)"
    prompt: "HTTP callback endpoint URL"
    password: false
  - name: HTTP_CALLBACK_API_KEY
    description: "API key sent as X-Api-Key header on every POST"
    prompt: "HTTP callback API key"
    password: true
  - name: HTTP_CALLBACK_WEBHOOK_BASE
    description: "Base URL of the webhook platform (default: http://localhost:8644) used to build webhook_url"
    prompt: "Webhook base URL"
    password: false
YAML

cat > "$PLUGIN_DIR/__init__.py" <<'PY'
from .adapter import register

__all__ = ["register"]
PY

cat > "$PLUGIN_DIR/adapter.py" <<'PY'
"""http_callback platform adapter (Hermes plugin).

Stateless outbound-only adapter: every ``send()`` POSTs the agent's reply
to a user-defined HTTP endpoint as JSON::

    {
      "route":       "<webhook route name>",
      "delivery_id": "<unique id for this delivery>",
      "reply":       "<agent response text>",
      "webhook_url": "<base>/webhooks/<route>"
    }

with header ``X-Api-Key: <key>``. Use it as the ``--deliver`` target of a
webhook subscription so the agent's answer lands in YOUR backend/database
instead of a chat platform.

Configuration in config.yaml::

    platforms:
      http_callback:
        enabled: true
        extra:
          endpoint_url: "https://api.example.com/v1/hermes-reply"
          api_key: "random123"
          webhook_base: "http://localhost:8644"   # optional

Environment variables (env wins over config ``extra``):

    HTTP_CALLBACK_ENDPOINT_URL   Required endpoint URL
    HTTP_CALLBACK_API_KEY        API key for the X-Api-Key header
    HTTP_CALLBACK_WEBHOOK_BASE   Base URL used to build webhook_url (default: http://localhost:8644)
"""

import logging
import os
import uuid
from typing import Any, Dict, Optional

try:
    import httpx
    HTTPX_AVAILABLE = True
except ImportError:  # pragma: no cover
    HTTPX_AVAILABLE = False
    httpx = None  # type: ignore[assignment]

from gateway.config import Platform, PlatformConfig
from gateway.platforms.base import (
    BasePlatformAdapter,
    SendResult,
)

logger = logging.getLogger(__name__)

DEFAULT_WEBHOOK_BASE = "http://localhost:8644"
TIMEOUT_SECONDS = 20.0


def _env_enablement(ctx=None):
    """Auto-enable when HTTP_CALLBACK_ENDPOINT_URL is set (env-only setups)."""
    return bool(os.getenv("HTTP_CALLBACK_ENDPOINT_URL"))


class HttpCallbackAdapter(BasePlatformAdapter):
    """POST agent replies to a user-defined HTTP endpoint."""

    MAX_MESSAGE_LENGTH = 100_000  # no practical limit; kept for parity

    def __init__(self, config: PlatformConfig):
        platform = Platform("http_callback")
        super().__init__(config=config, platform=platform)

        extra = config.extra or {}
        self._endpoint_url: str = (
            extra.get("endpoint_url") or os.getenv("HTTP_CALLBACK_ENDPOINT_URL", "")
        ).rstrip("/")
        self._api_key: str = extra.get("api_key") or os.getenv("HTTP_CALLBACK_API_KEY", "")
        self._webhook_base: str = (
            extra.get("webhook_base") or os.getenv("HTTP_CALLBACK_WEBHOOK_BASE")
            or DEFAULT_WEBHOOK_BASE
        ).rstrip("/")

        self._http_client: Optional["httpx.AsyncClient"] = None

    # -- Connection lifecycle -----------------------------------------------

    async def connect(self, *, is_reconnect: bool = False) -> bool:
        if not HTTPX_AVAILABLE:
            logger.error("[http_callback] httpx not available")
            return False
        if not self._endpoint_url:
            logger.error(
                "[http_callback] endpoint_url not configured "
                "(set platforms.http_callback.extra.endpoint_url or HTTP_CALLBACK_ENDPOINT_URL)"
            )
            return False
        self._http_client = httpx.AsyncClient(timeout=TIMEOUT_SECONDS)
        logger.info("[http_callback] connected (endpoint=%s)", self._endpoint_url)
        return True

    async def disconnect(self) -> None:
        if self._http_client:
            try:
                await self._http_client.aclose()
            except Exception:
                pass
            self._http_client = None

    # -- Sending ------------------------------------------------------------

    async def send(
        self,
        chat_id: str,
        content: str,
        reply_to: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> SendResult:
        """POST the agent reply to the configured endpoint as JSON."""
        if not self._http_client:
            return SendResult(success=False, error="HTTP client not initialized")

        # chat_id from webhook cross-platform delivery is the route name
        # (set via --deliver-chat-id "<route>"); fall back to a generic label.
        route = (chat_id or "").strip() or "webhook"
        delivery_id = str(uuid.uuid4())
        webhook_url = f"{self._webhook_base}/webhooks/{route}"

        payload = {
            "route": route,
            "delivery_id": delivery_id,
            "reply": content,
            "webhook_url": webhook_url,
        }

        headers = {"Content-Type": "application/json"}
        if self._api_key:
            headers["X-Api-Key"] = self._api_key

        try:
            resp = await self._http_client.post(
                self._endpoint_url,
                json=payload,
                headers=headers,
            )
            if resp.status_code < 300:
                logger.info(
                    "[http_callback] Delivered route=%s delivery=%s status=%d",
                    route, delivery_id, resp.status_code,
                )
                return SendResult(success=True, message_id=delivery_id)
            body_text = resp.text
            logger.warning(
                "[http_callback] Send failed HTTP %d: %s",
                resp.status_code, body_text[:200],
            )
            return SendResult(
                success=False, error=f"HTTP {resp.status_code}: {body_text[:200]}"
            )
        except httpx.TimeoutException:
            return SendResult(success=False, error="Timeout POSTing to endpoint")
        except Exception as e:  # pragma: no cover
            logger.error("[http_callback] Send error: %s", e)
            return SendResult(success=False, error=str(e))

    async def send_typing(self, chat_id: str, metadata=None) -> None:
        """No typing indicator for an HTTP callback."""
        pass

    async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
        return {"name": chat_id or "http_callback", "type": "dm"}


# -- Registration ------------------------------------------------------------


def check_requirements() -> bool:
    return HTTPX_AVAILABLE


def validate_config(config: PlatformConfig) -> bool:
    extra = config.extra or {}
    return bool(extra.get("endpoint_url") or os.getenv("HTTP_CALLBACK_ENDPOINT_URL"))


def is_connected(config: PlatformConfig) -> bool:
    return validate_config(config)


def register(ctx) -> None:
    """Plugin entry point — called by the Hermes plugin system at startup."""
    ctx.register_platform(
        name="http_callback",
        label="HTTP Callback",
        adapter_factory=lambda cfg: HttpCallbackAdapter(cfg),
        check_fn=check_requirements,
        validate_config=validate_config,
        is_connected=is_connected,
        required_env=["HTTP_CALLBACK_ENDPOINT_URL"],
        install_hint="pip install httpx   # already a Hermes dependency",
        env_enablement_fn=_env_enablement,
        max_message_length=100_000,
        emoji="🔗",
        pii_safe=True,
        allow_update_command=True,
        platform_hint=(
            "Replies are POSTed as JSON to your configured HTTP endpoint "
            "(X-Api-Key header auth). Use for saving agent responses to a "
            "database or forwarding to your backend."
        ),
    )
PY

ok "plugin files dibuat di $PLUGIN_DIR (plugin.yaml, __init__.py, adapter.py)"

# ── Enable plugin + config ──────────────────────────────────────────────────
step "4/7 — Enable plugin & set config"
"$HERMES_BIN" plugins enable http-callback-platform >/dev/null 2>&1 || \
  warn "plugins enable gagal (mungkin sudah enabled / perlu --allow-tool-override)"
ok "http-callback-platform enabled"

"$HERMES_BIN" config set platforms.http_callback.enabled true >/dev/null 2>&1
"$HERMES_BIN" config set platforms.http_callback.extra.endpoint_url "$ENDPOINT_URL" >/dev/null 2>&1
if [ -n "$API_KEY" ]; then
  "$HERMES_BIN" config set platforms.http_callback.extra.api_key "$API_KEY" >/dev/null 2>&1
fi
if [ -n "$WEBHOOK_BASE" ]; then
  "$HERMES_BIN" config set platforms.http_callback.extra.webhook_base "$WEBHOOK_BASE" >/dev/null 2>&1
fi
ok "config http_callback di-set (endpoint=$ENDPOINT_URL)"

# ── Restart gateway & verifikasi ────────────────────────────────────────────
step "5/7 — Restart gateway & verifikasi koneksi plugin"
"$HERMES_BIN" gateway restart >/dev/null 2>&1 || warn "gateway restart gagal — cek manual: hermes gateway restart"
sleep 6

LOG_FILE="$HERMES_HOME/logs/gateway.log"
if grep -q "✓ http_callback connected" "$LOG_FILE" 2>/dev/null; then
  ok "http_callback connected (lihat gateway.log)"
else
  warn "http_callback belum terlihat connected di log — cek: grep http_callback $LOG_FILE"
fi

# Health check webhook
if curl -sf -m 5 "http://localhost:8644/health" >/dev/null 2>&1; then
  ok "webhook health OK (localhost:8644)"
else
  warn "webhook health belum OK — pastikan gateway jalan & port 8644 terbuka"
fi

# ── Buat subscription unik ──────────────────────────────────────────────────
step "6/7 — Buat webhook subscription UNIK: $NAMA_UNIK"
SUB_OUTPUT=$("$HERMES_BIN" webhook subscribe "$NAMA_UNIK" \
  --prompt "Pesan dari user via webhook: {message}. Balas dalam Bahasa Indonesia, ringkas dan membantu." \
  --events "$NAMA_UNIK" \
  --description "Chat via webhook untuk mesin $NAMA_UNIK (callback ke backend)" \
  --deliver http_callback \
  --deliver-chat-id "$NAMA_UNIK" 2>&1) || { warn "subscribe gagal — cek: hermes webhook subscribe --help"; SUB_OUTPUT=""; }

echo "$SUB_OUTPUT" | sed 's/^/  /'
SUB_SECRET=$(echo "$SUB_OUTPUT" | grep -oE 'Secret: [A-Za-z0-9_-]+' | awk '{print $2}' | head -1)
if [ -z "$SUB_SECRET" ]; then
  # Fallback: ambil dari file subscription
  SUB_SECRET=$(python3 -c "
import json,os
p=os.path.expanduser('$HERMES_HOME/webhook_subscriptions.json')
try:
    d=json.load(open(p)); print(d.get('$NAMA_UNIK',{}).get('secret',''))
except Exception: print('')
" 2>/dev/null)
fi
if [ -n "$SUB_SECRET" ]; then
  ok "Secret HMAC subscription: $SUB_SECRET"
else
  warn "Secret tidak terbaca — lihat output subscribe di atas / hermes webhook list"
fi

# ── Tes preflight ke backend ────────────────────────────────────────────────
step "7/7 — Tes preflight ke backend Anda"
AUTH_HEADER=""
[ -n "$API_KEY" ] && AUTH_HEADER=(-H "X-Api-Key: $API_KEY")
PREFLIGHT_CODE=$(curl -s -o /tmp/setup_mesin_preflight.txt -w "%{http_code}" \
  -X POST -H "Content-Type: application/json" "${AUTH_HEADER[@]}" \
  -d "{\"route\":\"$NAMA_UNIK\",\"delivery_id\":\"preflight-setup\",\"reply\":\"tes koneksi dari setup-mesin.sh\",\"webhook_url\":\"${WEBHOOK_BASE:-http://localhost:8644}/webhooks/$NAMA_UNIK\"}" \
  "$ENDPOINT_URL" 2>/dev/null || echo "000")

if [[ "$PREFLIGHT_CODE" =~ ^2[0-9][0-9]$ ]]; then
  ok "Preflight OK — backend menerima POST (HTTP $PREFLIGHT_CODE)"
elif [ "$PREFLIGHT_CODE" = "000" ]; then
  warn "Preflight gagal — endpoint tidak terjangkau (timeout/DNS)"
else
  warn "Preflight: HTTP $PREFLIGHT_CODE (bukan 2xx) — cek backend Anda:"
  sed 's/^/    /' /tmp/setup_mesin_preflight.txt 2>/dev/null | head -5
fi

# ── Ringkasan ───────────────────────────────────────────────────────────────
step "SELESAI — Ringkasan identitas mesin: $NAMA_UNIK"
echo
echo "${C_BOLD}Webhook URL (input):${C_RESET}"
echo "  ${WEBHOOK_BASE:-http://localhost:8644}/webhooks/$NAMA_UNIK"
echo
echo "${C_BOLD}Identitas di payload callback (ke backend):${C_RESET}"
echo "  route        : $NAMA_UNIK"
echo "  delivery_id  : <uuid unik per balasan>"
echo "  reply        : <teks balasan agent>"
echo "  webhook_url  : ${WEBHOOK_BASE:-http://localhost:8644}/webhooks/$NAMA_UNIK"
echo
echo "${C_BOLD}Kredensial pengirim (untuk POST ke webhook):${C_RESET}"
echo "  secret HMAC  : ${SUB_SECRET:-<lihat: hermes webhook list>}"
echo "  event_type   : $NAMA_UNIK (wajib di body JSON)"
echo
echo "${C_BOLD}Backend callback:${C_RESET}"
echo "  endpoint     : $ENDPOINT_URL"
echo "  X-Api-Key    : ${API_KEY:-(tanpa auth)}"
echo
echo "Verifikasi alur penuh:"
echo "  python3 - << 'EOF'"
echo "  import hmac, hashlib, json, urllib.request"
echo "  url='${WEBHOOK_BASE:-http://localhost:8644}/webhooks/$NAMA_UNIK'"
echo "  secret='${SUB_SECRET:-YOUR_SECRET}'"
echo "  body=json.dumps({'event_type':'$NAMA_UNIK','message':'tes'}).encode()"
echo "  sig=hmac.new(secret.encode(),body,hashlib.sha256).hexdigest()"
echo "  req=urllib.request.Request(url,data=body,method='POST')"
echo "  req.add_header('Content-Type','application/json')"
echo "  req.add_header('X-Hub-Signature-256','sha256='+sig)"
echo "  print(urllib.request.urlopen(req,timeout=30).read().decode())"
echo "  EOF"
echo
echo "Cek delivery di log:  grep -i http_callback $HERMES_HOME/logs/gateway.log | tail -5"
echo "  SUKSES: [http_callback] Delivered route=$NAMA_UNIK delivery=<uuid> status=200"
echo
exit 0
