/**
 * relay-policy — Nostr relay write-policy sidecar
 *
 * Receives strfry plugin decision requests (one JSON object per HTTP POST to
 * /decide) and forwards them to the API server's /api/relay/policy endpoint.
 * Translates the API server's structured response into the flat decision JSON
 * that strfry's plugin protocol expects.
 *
 * Environment:
 *   PORT                  — HTTP port to listen on (default: 3080)
 *   RELAY_API_URL         — base URL of the API server, no trailing slash
 *                           e.g. https://alexanderwhitestone.com
 *   RELAY_POLICY_SECRET   — shared secret; sent as Bearer token to the API
 *                           so the /api/relay/policy route can verify origin
 *
 * strfry plugin stdin/stdout protocol:
 *   Input  per event: {"event":{...},"receivedAt":N,"sourceType":"...","sourceInfo":"..."}
 *   Output per event: {"id":"<event-id>","action":"accept|reject|shadowReject","msg":"..."}
 */

import * as http from "http";

const PORT    = parseInt(process.env["PORT"] ?? "3080", 10);
const API_URL = (process.env["RELAY_API_URL"] ?? "").replace(/\/$/, "");
const SECRET  = process.env["RELAY_POLICY_SECRET"] ?? "";

if (!API_URL) {
  console.error("[relay-policy] RELAY_API_URL is not set — rejecting all events");
}
if (!SECRET) {
  console.warn("[relay-policy] RELAY_POLICY_SECRET is not set — all API calls will be unauthenticated");
}

// ── Decision types ────────────────────────────────────────────────────────────

type StrfryAction = "accept" | "reject" | "shadowReject";

interface StrfryInput {
  event: {
    id: string;
    pubkey: string;
    kind: number;
    created_at: number;
    tags: string[][];
    content: string;
    sig: string;
  };
  receivedAt: number;
  sourceType: string;
  sourceInfo: string;
}

interface StrfryDecision {
  id: string;
  action: StrfryAction;
  msg: string;
}

// ── Helpers ───────────────────────────────────────────────────────────────────

function rejectDecision(id: string, msg: string): StrfryDecision {
  return { id, action: "reject", msg };
}

/**
 * Forward the strfry event payload to the API server and return its decision.
 * Falls back to reject if the API call fails or times out.
 */
async function fetchPolicyDecision(payload: StrfryInput): Promise<StrfryDecision> {
  const eventId = payload.event?.id ?? "unknown";

  if (!API_URL) {
    return rejectDecision(eventId, "relay-policy: no API URL configured");
  }

  const url = `${API_URL}/api/relay/policy`;
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
  };
  if (SECRET) {
    headers["Authorization"] = `Bearer ${SECRET}`;
  }

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 5_000);

  try {
    const res = await fetch(url, {
      method: "POST",
      headers,
      body: JSON.stringify(payload),
      signal: controller.signal,
    });
    clearTimeout(timer);

    if (!res.ok) {
      console.error(`[relay-policy] API returned ${res.status} for event ${eventId.slice(0, 8)}`);
      return rejectDecision(eventId, `policy API error: HTTP ${res.status}`);
    }

    const json = await res.json() as { action?: StrfryAction; msg?: string };
    const action: StrfryAction =
      json.action === "accept" || json.action === "shadowReject" ? json.action : "reject";

    return { id: eventId, action, msg: json.msg ?? "" };
  } catch (err) {
    clearTimeout(timer);
    const reason = err instanceof Error ? err.message : String(err);
    console.warn(`[relay-policy] policy fetch failed for ${eventId.slice(0, 8)}: ${reason}`);
    return rejectDecision(eventId, `policy fetch failed: ${reason}`);
  }
}

// ── HTTP server ───────────────────────────────────────────────────────────────

const server = http.createServer(async (req, res) => {
  const url  = req.url ?? "/";
  const method = req.method ?? "GET";

  // Health-check
  if (method === "GET" && url === "/health") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ ok: true, apiUrl: API_URL || null }));
    return;
  }

  // Decision endpoint — called by relay-policy-plugin shell script
  if (method === "POST" && url === "/decide") {
    let body = "";
    req.on("data", chunk => { body += chunk; });
    req.on("end", async () => {
      let payload: StrfryInput;
      try {
        payload = JSON.parse(body) as StrfryInput;
      } catch {
        res.writeHead(400, { "Content-Type": "application/json" });
        res.end(JSON.stringify(rejectDecision("unknown", "invalid JSON")));
        return;
      }

      const decision = await fetchPolicyDecision(payload);
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify(decision));
    });
    return;
  }

  res.writeHead(404, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ error: "not found" }));
});

server.listen(PORT, "0.0.0.0", () => {
  console.log(`[relay-policy] listening on :${PORT}`);
  console.log(`[relay-policy] API URL: ${API_URL || "(not set — all events will be rejected)"}`);
});

server.on("error", (err) => {
  console.error("[relay-policy] server error:", err);
  process.exit(1);
});
