/**
 * relay.ts — Nostr relay write-policy API
 *
 * POST /api/relay/policy
 *   Internal endpoint called exclusively by the relay-policy sidecar.
 *   Protected by RELAY_POLICY_SECRET Bearer token.
 *
 *   Policy tiers:
 *     no relay_account row / "read" / "none" → reject
 *     "write" + tier != "elite"              → enqueue + shadowReject
 *     "write" + tier == "elite"              → inject into strfry + accept
 *
 * GET /api/relay/policy
 *   Health + roundtrip probe (no auth).
 */

import { Router, type Request, type Response } from "express";
import { db, nostrIdentities } from "@workspace/db";
import { eq } from "drizzle-orm";
import { makeLogger } from "../lib/logger.js";
import { relayAccountService } from "../lib/relay-accounts.js";
import { moderationService } from "../lib/moderation.js";
import { injectEvent } from "../lib/strfry.js";

const logger = makeLogger("relay-policy");
const router = Router();

const RELAY_POLICY_SECRET = process.env["RELAY_POLICY_SECRET"] ?? "";
const IS_PROD = process.env["NODE_ENV"] === "production";

if (!RELAY_POLICY_SECRET) {
  if (IS_PROD) {
    logger.error(
      "RELAY_POLICY_SECRET is not set in production — " +
      "POST /api/relay/policy is open to any caller.",
    );
  } else {
    logger.warn("RELAY_POLICY_SECRET not set — /api/relay/policy accepts local-only requests (dev mode)");
  }
}

// ── Types ─────────────────────────────────────────────────────────────────────

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

interface NostrEventPayload {
  id: string;
  pubkey: string;
  kind: number;
  created_at: number;
  tags: string[][];
  content: string;
  sig: string;
}

interface PolicyRequest {
  event: NostrEventPayload;
  receivedAt: number;
  sourceType: string;
  sourceInfo: string;
}

interface PolicyDecision {
  id: string;
  action: PolicyAction;
  msg: string;
}

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

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

function acceptDecision(id: string): PolicyDecision {
  return { id, action: "accept", msg: "" };
}

function shadowRejectDecision(id: string): PolicyDecision {
  return { id, action: "shadowReject", msg: "" };
}

// ── GET /relay/policy ─────────────────────────────────────────────────────────

router.get("/relay/policy", async (_req: Request, res: Response) => {
  res.json({
    ok: true,
    secretConfigured: !!RELAY_POLICY_SECRET,
    info: "Relay policy active. write+elite → accept; write+non-elite → moderation queue; read/none → reject.",
  });
});

// ── Auth middleware ───────────────────────────────────────────────────────────

function checkRelayAuth(req: Request, res: Response): boolean {
  if (RELAY_POLICY_SECRET) {
    const authHeader = req.headers["authorization"] ?? "";
    const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : "";
    if (token !== RELAY_POLICY_SECRET) {
      res.status(401).json({ error: "Unauthorized" });
      return false;
    }
    return true;
  }

  const ip = req.ip ?? "";
  const isLocal = ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1";
  if (!isLocal) {
    logger.warn("relay/policy: no secret configured, rejecting non-local call", { ip });
    res.status(401).json({ error: "Unauthorized" });
    return false;
  }
  logger.warn("relay/policy: RELAY_POLICY_SECRET not set — accepting local-only call");
  return true;
}

// ── POST /relay/policy ────────────────────────────────────────────────────────

router.post("/relay/policy", async (req: Request, res: Response) => {
  if (!checkRelayAuth(req, res)) return;

  const body = req.body as Partial<PolicyRequest>;
  const event = body.event;

  if (!event || typeof event !== "object") {
    res.status(400).json({ error: "Missing 'event' in request body" });
    return;
  }

  const eventId = typeof event.id === "string" ? event.id : "unknown";
  const pubkey  = typeof event.pubkey === "string" ? event.pubkey : "";
  const kind    = typeof event.kind === "number" ? event.kind : -1;

  const decision = await evaluatePolicy(event, eventId, pubkey, kind);

  logger.info("relay policy decision", {
    eventId: eventId.slice(0, 8),
    pubkey: pubkey.slice(0, 8),
    kind,
    action: decision.action,
    sourceType: body.sourceType,
  });

  res.json(decision);
});

/**
 * Core write-policy evaluation.
 *
 * 1. No pubkey → reject
 * 2. Not in relay_accounts with "write" → reject (or "read-only" msg)
 * 3. "write" + elite tier → inject into strfry + accept (elite bypass)
 * 4. "write" + non-elite → enqueue into moderation + shadowReject
 */
async function evaluatePolicy(
  rawEvent: Partial<NostrEventPayload>,
  eventId: string,
  pubkey: string,
  kind: number,
): Promise<PolicyDecision> {
  if (!pubkey) {
    return rejectDecision(eventId, "missing pubkey");
  }

  // ── Step 1: Check relay access ─────────────────────────────────────────────
  let accessLevel: string;
  try {
    accessLevel = await relayAccountService.getAccess(pubkey);
  } catch (err) {
    logger.error("relay-accounts lookup failed — defaulting to reject", { err });
    return rejectDecision(eventId, "policy service error — try again later");
  }

  if (accessLevel === "read") {
    return rejectDecision(eventId, "read-only access — write not permitted");
  }

  if (accessLevel !== "write") {
    return rejectDecision(eventId, "pubkey not whitelisted for this relay");
  }

  // ── Step 2: Check trust tier (elite bypass) ────────────────────────────────
  let isElite = false;
  try {
    const rows = await db
      .select({ tier: nostrIdentities.tier })
      .from(nostrIdentities)
      .where(eq(nostrIdentities.pubkey, pubkey))
      .limit(1);
    isElite = rows[0]?.tier === "elite";
  } catch (err) {
    logger.error("tier lookup failed — treating as non-elite", { err });
  }

  if (isElite) {
    // Elite accounts bypass moderation — inject directly into strfry.
    // On inject failure, return hard reject so the client knows to retry
    // (shadowReject would silently drop the event from the sender's perspective).
    const rawJson = JSON.stringify(rawEvent);
    const injectResult = await injectEvent(rawJson);
    if (!injectResult.ok) {
      logger.warn("elite event inject failed — returning reject so client can retry", {
        eventId: eventId.slice(0, 8),
        error: injectResult.error,
      });
      return rejectDecision(eventId, "relay unavailable — please retry");
    }
    return acceptDecision(eventId);
  }

  // ── Step 3: Non-elite write — enqueue for moderation ──────────────────────
  try {
    await moderationService.enqueue({
      id: eventId,
      pubkey,
      kind,
      rawJson: JSON.stringify(rawEvent),
    });
  } catch (err) {
    logger.error("failed to enqueue event for moderation", {
      eventId: eventId.slice(0, 8),
      err,
    });
    // Fail-closed: reject if we can't queue
    return rejectDecision(eventId, "moderation service error — try again later");
  }

  // shadowReject: strfry reports "ok" to the sender but doesn't publish
  return shadowRejectDecision(eventId);
}

export default router;
