const crypto = require("crypto");
const express = require("express");
const path = require("path");
const discord = require("discord.js");
const { log } = require("../../src/services/logger");
const { getPlatformStatus } = require("../../src/services/platforms");
const placeholderService = require("../../src/services/placeholders");

const PLUGIN_ID = "throne_wishlist";
const NAMESPACE = "throne";
const REPLAY_WINDOW_SECONDS = 300;
const DEBUG_IDLE_MS = 300000;
const DEBUG_MAX_MS = 1800000;
const THRONE_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAPXbUfxh7XL4SYUVcfhmYMIbxvtR9E9LDd8gPJ1PwSD8=
-----END PUBLIC KEY-----`;
const EVENT_TYPES = [
  "gift_purchased",
  "contribution_purchased",
  "gift_crowdfunded"
];
const PLATFORMS = ["discord", "twitch", "youtube", "kick"];
const DEFAULT_TEMPLATES = {
  gift_purchased:
    "🎁 {{gifter_username}} bought {{item_name}} from {{creator_username}}'s Throne wishlist!",
  contribution_purchased:
    "💝 {{gifter_username}} contributed {{amount_display}} toward {{item_name}} on {{creator_username}}'s Throne wishlist!",
  gift_crowdfunded:
    "🎉 {{item_name}} was fully crowdfunded on {{creator_username}}'s Throne wishlist!"
};
const PLACEHOLDERS = new Set([
  "contract_version",
  "event_id",
  "event_type",
  "webhook_identifier",
  "webhook_uuid",
  "received_at",
  "received_at_iso",
  "creator_id",
  "creator_username",
  "gifter_username",
  "message",
  "item_name",
  "item_thumbnail_url",
  "price",
  "amount",
  "currency",
  "is_surprise_gift",
  "price_display",
  "amount_display",
  "value",
  "value_display",
  "payload_json",
  "data.creator_id",
  "data.creator_username",
  "data.gifter_username",
  "data.message",
  "data.item_name",
  "data.item_thumbnail_url",
  "data.price",
  "data.amount",
  "data.currency",
  "data.is_surprise_gift"
]);
const debugSessions = new Map();

module.exports = {
  id: PLUGIN_ID,
  init({
    web,
    webhooks,
    placeholders = placeholderService,
    db,
    settings,
    discordClient,
    twitchClient,
    youtubeClient
  }) {
    ensureTables(db);
    ensureDefaults(db);
    registerPlaceholderSupport(placeholders);
    const webhookApi = webhooks || global.lumiFrameworks?.webhooks || null;
    registerStoredEndpoints({ db, webhookApi, clients: { discordClient, twitchClient, youtubeClient } });

    const router = web.createRouter();
    router.use("/assets", express.static(path.join(__dirname, "public")));

    router.get("/", async (req, res) => {
      if (!isAdmin(req.session.user)) {
        return renderDenied(res);
      }
      const baseUrl = getPublicBaseUrl(req, settings);
      const endpoints = listEndpoints(db).map((endpoint) => ({
        ...endpoint,
        publicUrl: webhookApi
          ? webhookApi.buildPublicUrl({
              namespace: NAMESPACE,
              slug: endpoint.slug,
              req,
              baseUrl: baseUrl.value
            })
          : `/webhooks/${NAMESPACE}/${endpoint.slug}`
      }));
      const platformStatus = await buildPlatformStatus({
        settings,
        db,
        discordClient,
        twitchClient,
        youtubeClient
      });
      res.render(path.join(__dirname, "views", "admin.ejs"), {
        title: "Throne Wishlist",
        endpoints,
        templates: listTemplates(db),
        destinations: listDestinations(db),
        discordChannels: getDiscordChannels(discordClient),
        platformStatus,
        activePlatforms: platformStatus
          .filter((platform) => platform.sendAvailable)
          .map((platform) => platform.id),
        eventTypes: EVENT_TYPES,
        platforms: PLATFORMS,
        placeholders: Array.from(PLACEHOLDERS).sort(),
        webhookAvailable: Boolean(webhookApi),
        publicBaseUrl: baseUrl.value,
        publicBaseUrlWarning: baseUrl.warning,
        formatTimestamp
      });
    });

    router.post("/endpoints/create", (req, res) => {
      if (!isAdmin(req.session.user)) {
        return renderDenied(res);
      }
      if (!webhookApi) {
        req.session.flash = { type: "error", message: "Core webhook framework is unavailable." };
        return res.redirect(`/plugins/${PLUGIN_ID}`);
      }
      const identifier = (req.body.identifier || "").trim();
      try {
        const uuid = crypto.randomUUID();
        const slug = webhookApi.generateSlug({ identifier, uuid });
        const now = Date.now();
        db.prepare(
          "INSERT INTO throne_webhook_endpoints (id, identifier, slug, uuid, enabled, created_at, updated_at) VALUES (?, ?, ?, ?, 1, ?, ?)"
        ).run(uuid, identifier, slug, uuid, now, now);
        registerEndpoint({
          db,
          webhookApi,
          endpoint: getEndpoint(db, uuid),
          clients: { discordClient, twitchClient, youtubeClient }
        });
        req.session.flash = { type: "success", message: "Throne webhook endpoint created." };
      } catch (error) {
        req.session.flash = { type: "error", message: error?.message || "Unable to create endpoint." };
      }
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/endpoints/:id/renew", (req, res) => {
      if (!isAdmin(req.session.user)) {
        return renderDenied(res);
      }
      const endpoint = getEndpoint(db, req.params.id);
      if (!endpoint || !webhookApi) {
        req.session.flash = { type: "error", message: "Endpoint or webhook framework unavailable." };
        return res.redirect(`/plugins/${PLUGIN_ID}`);
      }
      try {
        webhookApi.unregisterInbound({ pluginId: PLUGIN_ID, endpointId: endpoint.id });
        const uuid = crypto.randomUUID();
        const slug = webhookApi.generateSlug({ identifier: endpoint.identifier, uuid });
        db.prepare(
          "UPDATE throne_webhook_endpoints SET slug = ?, uuid = ?, updated_at = ? WHERE id = ?"
        ).run(slug, uuid, Date.now(), endpoint.id);
        registerEndpoint({
          db,
          webhookApi,
          endpoint: getEndpoint(db, endpoint.id),
          clients: { discordClient, twitchClient, youtubeClient }
        });
        req.session.flash = { type: "success", message: "Endpoint renewed. The old URL is invalid." };
      } catch (error) {
        req.session.flash = { type: "error", message: error?.message || "Unable to renew endpoint." };
      }
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/endpoints/:id/remove", (req, res) => {
      if (!isAdmin(req.session.user)) {
        return renderDenied(res);
      }
      const endpoint = getEndpoint(db, req.params.id);
      if (endpoint) {
        webhookApi?.unregisterInbound({ pluginId: PLUGIN_ID, endpointId: endpoint.id });
        db.prepare("DELETE FROM throne_webhook_endpoints WHERE id = ?").run(endpoint.id);
      }
      req.session.flash = { type: "success", message: "Endpoint removed." };
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/destinations", async (req, res) => {
      if (!isAdmin(req.session.user)) {
        return renderDenied(res);
      }
      const enabled = new Set(normalizeArray(req.body.enabled_platforms));
      const discordChannelId = (req.body.discord_destination_id || "").trim();
      if (enabled.has("discord")) {
        const validation = await validateDiscordChannel(discordClient, discordChannelId);
        if (!validation.valid) {
          req.session.flash = { type: "error", message: validation.message };
          return res.redirect(`/plugins/${PLUGIN_ID}`);
        }
      }
      const platformStatus = await buildPlatformStatus({
        settings,
        db,
        discordClient,
        twitchClient,
        youtubeClient
      });
      const statusMap = new Map(platformStatus.map((entry) => [entry.id, entry]));
      for (const platform of PLATFORMS) {
        const canEnable = statusMap.get(platform)?.sendAvailable;
        const isEnabled = enabled.has(platform) && canEnable;
        const destinationId = platform === "discord" ? discordChannelId : null;
        db.prepare(
          "UPDATE throne_platform_destinations SET enabled = ?, destination_id = ?, destination_label = ?, updated_at = ? WHERE platform = ?"
        ).run(
          isEnabled ? 1 : 0,
          destinationId,
          platform === "discord" ? statusMap.get(platform)?.destinationLabel || null : null,
          Date.now(),
          platform
        );
      }
      req.session.flash = { type: "success", message: "Platform destinations saved." };
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/templates", (req, res) => {
      if (!isAdmin(req.session.user)) {
        return renderDenied(res);
      }
      const eventType = EVENT_TYPES.includes(req.body.event_type)
        ? req.body.event_type
        : null;
      const platform = PLATFORMS.includes(req.body.platform)
        ? req.body.platform
        : null;
      const template = (req.body.template || "").trim();
      const enabled = req.body.enabled === "on";
      if (!eventType || !platform) {
        req.session.flash = { type: "error", message: "Invalid event type or platform." };
        return res.redirect(`/plugins/${PLUGIN_ID}`);
      }
      if (enabled && !template) {
        req.session.flash = { type: "error", message: "Enabled templates cannot be empty." };
        return res.redirect(`/plugins/${PLUGIN_ID}`);
      }
      const unknown = findUnknownPlaceholders(template);
      const placeholderValidation = placeholders.validateTemplate({
        fieldId: "plugin.throne_wishlist.message_template",
        template,
        outputAudience: "user",
        user: req.session.user
      });
      if (!placeholderValidation.ok) {
        req.session.flash = {
          type: "error",
          message: `Template contains unavailable or unauthorized placeholder(s): ${
            placeholderValidation.errors.map((error) => error.token || error.id || error.reason).slice(0, 5).join(", ")
          }.`
        };
        return res.redirect(`/plugins/${PLUGIN_ID}`);
      }
      db.prepare(
        "UPDATE throne_event_message_templates SET enabled = ?, template = ?, updated_at = ? WHERE event_type = ? AND platform = ?"
      ).run(enabled ? 1 : 0, template, Date.now(), eventType, platform);
      req.session.flash = unknown.length
        ? { type: "error", message: `Template saved. Unknown placeholders remain unchanged: ${unknown.join(", ")}.` }
        : { type: "success", message: "Event template saved." };
      res.redirect(`/plugins/${PLUGIN_ID}`);
    });

    router.post("/debug/start", (req, res) => {
      if (!isAdmin(req.session.user)) {
        return res.status(403).json({ error: "Access denied." });
      }
      const id = crypto.randomUUID();
      const now = Date.now();
      debugSessions.set(id, {
        id,
        userId: req.session.user.id,
        createdAt: now,
        lastPayloadAt: now,
        response: null,
        idleTimer: setTimeout(() => closeDebugSession(id, "inactivity"), DEBUG_IDLE_MS),
        expiryTimer: setTimeout(() => closeDebugSession(id, "maximum lifetime"), DEBUG_MAX_MS)
      });
      res.json({ sessionId: id, expiresInSeconds: DEBUG_MAX_MS / 1000 });
    });

    router.get("/debug/stream/:sessionId", (req, res) => {
      if (!isAdmin(req.session.user)) {
        return res.status(403).end();
      }
      const session = debugSessions.get(req.params.sessionId);
      if (!session || session.userId !== req.session.user.id) {
        return res.status(404).end();
      }
      res.set({
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache, no-transform",
        Connection: "keep-alive"
      });
      res.flushHeaders?.();
      session.response = res;
      res.write(`event: ready\ndata: ${JSON.stringify({ sessionId: session.id })}\n\n`);
      req.on("close", () => closeDebugSession(session.id, "client disconnected"));
      return null;
    });

    router.post("/debug/stop/:sessionId", (req, res) => {
      if (!isAdmin(req.session.user)) {
        return res.status(403).json({ error: "Access denied." });
      }
      const session = debugSessions.get(req.params.sessionId);
      if (session?.userId === req.session.user.id) {
        closeDebugSession(session.id, "stopped");
      }
      res.status(204).end();
    });

    web.mount(`/plugins/${PLUGIN_ID}`, router, {
      label: "Throne Wishlist",
      role: "admin",
      section: "plugins"
    });
    ensureSidebarNavItem(settings);
  }
};

function ensureTables(db) {
  db.exec(`
    CREATE TABLE IF NOT EXISTS throne_webhook_endpoints (
      id TEXT PRIMARY KEY,
      identifier TEXT NOT NULL,
      slug TEXT NOT NULL UNIQUE,
      uuid TEXT NOT NULL UNIQUE,
      enabled INTEGER NOT NULL DEFAULT 1,
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL,
      last_payload_at INTEGER NULL,
      last_event_id TEXT NULL,
      last_event_type TEXT NULL,
      last_authentic INTEGER NULL,
      last_payload_preview TEXT NULL
    );

    CREATE TABLE IF NOT EXISTS throne_event_deliveries (
      event_id TEXT PRIMARY KEY,
      endpoint_id TEXT NOT NULL,
      event_type TEXT NOT NULL,
      authentic INTEGER NOT NULL,
      received_at INTEGER NOT NULL,
      processed_at INTEGER NULL,
      status TEXT NOT NULL,
      details TEXT NULL
    );

    CREATE TABLE IF NOT EXISTS throne_event_message_templates (
      id TEXT PRIMARY KEY,
      event_type TEXT NOT NULL,
      platform TEXT NOT NULL,
      enabled INTEGER NOT NULL DEFAULT 0,
      template TEXT NOT NULL,
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL,
      UNIQUE(event_type, platform)
    );

    CREATE TABLE IF NOT EXISTS throne_platform_destinations (
      platform TEXT PRIMARY KEY,
      enabled INTEGER NOT NULL DEFAULT 0,
      destination_id TEXT NULL,
      destination_label TEXT NULL,
      updated_at INTEGER NOT NULL
    );
  `);
}

function ensureDefaults(db) {
  const now = Date.now();
  for (const eventType of EVENT_TYPES) {
    for (const platform of PLATFORMS) {
      db.prepare(
        "INSERT OR IGNORE INTO throne_event_message_templates (id, event_type, platform, enabled, template, created_at, updated_at) VALUES (?, ?, ?, 0, ?, ?, ?)"
      ).run(crypto.randomUUID(), eventType, platform, DEFAULT_TEMPLATES[eventType], now, now);
    }
  }
  for (const platform of PLATFORMS) {
    db.prepare(
      "INSERT OR IGNORE INTO throne_platform_destinations (platform, enabled, destination_id, destination_label, updated_at) VALUES (?, 0, NULL, NULL, ?)"
    ).run(platform, now);
  }
}

function registerPlaceholderSupport(placeholders) {
  if (!placeholders?.registerFieldPolicy || !placeholders?.registerPlaceholders) return;
  placeholders.registerFieldPolicy({
    field_id: "plugin.throne_wishlist.message_template",
    label: "Throne Wishlist event message",
    field_type: "chat_message",
    output_audience: "user",
    min_editor_role: "admin",
    allowed_namespaces: ["plugin.throne_wishlist"],
    max_sensitivity: "public_safe"
  });
  placeholders.registerPlaceholders(Array.from(PLACEHOLDERS).sort().map((placeholder) => ({
    id: `plugin.throne_wishlist.${placeholder}`,
    namespace: "plugin.throne_wishlist",
    label: humanizePlaceholderLabel(placeholder),
    description: `Throne event value: ${placeholder}.`,
    value_type: placeholder === "payload_json" ? "json" : "string",
    sensitivity: placeholder === "payload_json" ? "admin" : "public_safe",
    min_editor_role: "admin",
    min_viewer_role: "user",
    allowed_field_types: ["chat_message"],
    plugin_id: PLUGIN_ID,
    aliases: [placeholder],
    resolver: ({ runtimeContext }) => {
      const tokens = runtimeContext?.throneTokens || runtimeContext?.tokens || {};
      return tokens[placeholder] ?? "";
    }
  })));
}

function humanizePlaceholderLabel(value) {
  return String(value || "")
    .split(".")
    .pop()
    .replace(/[_-]+/g, " ")
    .replace(/\b\w/g, (letter) => letter.toUpperCase());
}

function registerStoredEndpoints({ db, webhookApi, clients }) {
  if (!webhookApi) {
    log("warn", "Throne Wishlist webhook framework unavailable");
    return;
  }
  for (const endpoint of listEndpoints(db).filter((entry) => entry.enabled)) {
    try {
      registerEndpoint({ db, webhookApi, endpoint, clients });
    } catch (error) {
      log("error", "Throne endpoint registration failed", {
        endpointId: endpoint.id,
        message: error?.message || String(error)
      });
    }
  }
}

function registerEndpoint({ db, webhookApi, endpoint, clients }) {
  webhookApi.registerInbound({
    pluginId: PLUGIN_ID,
    endpointId: endpoint.id,
    namespace: NAMESPACE,
    slug: endpoint.slug,
    description: `Throne wishlist endpoint: ${endpoint.identifier}`,
    handler: (context) => handleIncoming({ db, endpointId: endpoint.id, context, clients })
  });
}

async function handleIncoming({
  db,
  endpointId,
  context,
  clients,
  signatureVerifier = verifyThroneSignature
}) {
  const endpoint = getEndpoint(db, endpointId);
  if (!endpoint?.enabled) {
    return { status: 404, body: { error: "Endpoint unavailable." } };
  }
  const timestamp = headerValue(context.headers, "x-signature-timestamp");
  const signature = headerValue(context.headers, "x-signature-ed25519");
  const verification = signatureVerifier({
    rawBody: context.rawBody,
    timestamp,
    signature
  });
  const payload = context.parsedJson;
  const eventType = typeof payload?.event_type === "string" ? payload.event_type : null;
  const eventId = typeof payload?.event_id === "string" ? payload.event_id : null;
  const documented = EVENT_TYPES.includes(eventType);
  let authenticityStatus = verification.status;
  if (verification.authentic && context.jsonError) {
    authenticityStatus = "parse_error";
  } else if (verification.authentic && eventType && !documented) {
    authenticityStatus = "unknown_event";
  }
  const preview = buildPayloadPreview({
    receivedAt: context.receivedAt,
    eventType,
    eventId,
    authentic: verification.authentic,
    status: authenticityStatus
  });
  db.prepare(
    "UPDATE throne_webhook_endpoints SET last_payload_at = ?, last_event_id = ?, last_event_type = ?, last_authentic = ?, last_payload_preview = ?, updated_at = ? WHERE id = ?"
  ).run(
    context.receivedAt,
    eventId,
    eventType,
    verification.authentic ? 1 : 0,
    preview,
    Date.now(),
    endpoint.id
  );

  const debugItem = {
    endpointId: endpoint.id,
    endpointIdentifier: endpoint.identifier,
    webhookUuid: endpoint.uuid,
    receivedAt: context.receivedAt,
    eventId,
    eventType,
    authentic: verification.authentic,
    authenticityStatus,
    payload,
    rawBodyPreview: context.jsonError ? context.rawBodyText.slice(0, 4000) : null
  };
  publishDebug(debugItem);
  writePayloadLog({
    endpoint,
    context,
    payload,
    eventId,
    eventType,
    verification,
    authenticityStatus
  });

  if (!verification.authentic) {
    return { status: verification.httpStatus, body: { error: "Invalid Throne signature." } };
  }
  if (context.jsonError || !payload) {
    return { status: 400, body: { error: "Invalid JSON payload." } };
  }
  if (!isValidEnvelope(payload)) {
    return { status: 400, body: { error: "Invalid Throne event envelope." } };
  }
  const existing = db.prepare(
    "SELECT event_id FROM throne_event_deliveries WHERE event_id = ?"
  ).get(eventId);
  if (existing) {
    log("info", `Webhook - Throne - ${EVENT_TYPES.includes(eventType) ? eventType : "unknown"}`, JSON.stringify({
      provider: "throne",
      authentic: true,
      authenticity_status: "duplicate",
      endpoint_id: endpoint.id,
      endpoint_identifier: endpoint.identifier,
      event_id: eventId,
      event_type: eventType,
      received_at: new Date(context.receivedAt).toISOString()
    }));
    return { status: 204 };
  }
  db.prepare(
    "INSERT INTO throne_event_deliveries (event_id, endpoint_id, event_type, authentic, received_at, processed_at, status, details) VALUES (?, ?, ?, 1, ?, NULL, ?, NULL)"
  ).run(eventId, endpoint.id, eventType, context.receivedAt, documented ? "accepted" : "skipped");
  if (!documented) {
    return { status: 202, body: { accepted: true, message: "Unknown event logged." } };
  }
  setImmediate(() => {
    sendPlatformMessages({
      db,
      payload,
      endpoint,
      receivedAt: context.receivedAt,
      clients
    }).catch((error) => {
      log("error", "Throne platform delivery failed", {
        eventId,
        message: error?.message || String(error)
      });
    });
  });
  return { status: 202, body: { accepted: true } };
}

function verifyThroneSignature({
  rawBody,
  timestamp,
  signature,
  publicKey = THRONE_PUBLIC_KEY,
  nowMs = Date.now()
}) {
  if (!timestamp || !signature) {
    return { authentic: false, status: "missing_signature", httpStatus: 401 };
  }
  if (!/^\d+$/.test(timestamp)) {
    return { authentic: false, status: "bad_timestamp", httpStatus: 400 };
  }
  if (!/^[0-9a-f]{128}$/i.test(signature)) {
    return { authentic: false, status: "bad_signature_length", httpStatus: 400 };
  }
  if (Math.abs(nowMs - Number(timestamp) * 1000) > REPLAY_WINDOW_SECONDS * 1000) {
    return { authentic: false, status: "replay_window", httpStatus: 401 };
  }
  const signatureBuffer = Buffer.from(signature, "hex");
  if (signatureBuffer.length !== 64) {
    return { authentic: false, status: "bad_signature_length", httpStatus: 400 };
  }
  const message = Buffer.concat([Buffer.from(`${timestamp}.`, "utf8"), rawBody]);
  try {
    const authentic = crypto.verify(null, message, publicKey, signatureBuffer);
    return {
      authentic,
      status: authentic ? "valid" : "invalid",
      httpStatus: authentic ? 204 : 401
    };
  } catch {
    return { authentic: false, status: "invalid", httpStatus: 401 };
  }
}

async function sendPlatformMessages({ db, payload, endpoint, receivedAt, clients }) {
  const eventId = payload.event_id;
  const tokens = buildTokens({ payload, endpoint, receivedAt });
  const rows = db.prepare(
    "SELECT t.platform, t.template, d.destination_id FROM throne_event_message_templates t " +
      "JOIN throne_platform_destinations d ON d.platform = t.platform " +
      "WHERE t.event_type = ? AND t.enabled = 1 AND d.enabled = 1"
  ).all(payload.event_type);
  const tasks = rows.map(async (row) => {
    const message = sanitizePlatformText(await renderTemplate(row.template, tokens));
    if (!message.trim()) {
      throw new Error(`Rendered ${row.platform} message is empty.`);
    }
    return sendToPlatform({
      platform: row.platform,
      destinationId: row.destination_id,
      message,
      clients
    });
  });
  const results = await Promise.allSettled(tasks);
  const details = rows.map((row, index) => ({
    platform: row.platform,
    success: results[index]?.status === "fulfilled",
    error: results[index]?.status === "rejected"
      ? results[index].reason?.message || String(results[index].reason)
      : null
  }));
  const failed = details.filter((entry) => !entry.success);
  const status = !details.length
    ? "skipped"
    : failed.length
      ? (failed.length === details.length ? "failed" : "partial")
      : "sent";
  db.prepare(
    "UPDATE throne_event_deliveries SET processed_at = ?, status = ?, details = ? WHERE event_id = ?"
  ).run(
    Date.now(),
    status,
    JSON.stringify(details),
    eventId
  );
  for (const failure of failed) {
    log("warn", "Throne destination send failed", {
      eventId,
      platform: failure.platform,
      message: failure.error
    });
  }
}

async function sendToPlatform({ platform, destinationId, message, clients }) {
  if (platform === "discord") {
    const validation = await validateDiscordChannel(clients.discordClient, destinationId);
    if (!validation.valid || !validation.channel) {
      throw new Error(validation.message);
    }
    return validation.channel.send({ content: message, allowedMentions: { parse: [] } });
  }
  if (platform === "twitch") {
    if (!clients.twitchClient || typeof clients.twitchClient.say !== "function") {
      throw new Error("Twitch send capability is unavailable.");
    }
    const channels = typeof clients.twitchClient.getChannels === "function"
      ? clients.twitchClient.getChannels()
      : [];
    const channel = channels?.[0];
    if (!channel) {
      throw new Error("No connected Twitch channel is available.");
    }
    return clients.twitchClient.say(channel, message);
  }
  if (platform === "youtube") {
    if (!clients.youtubeClient || typeof clients.youtubeClient.sendMessage !== "function") {
      throw new Error("YouTube send capability is unavailable.");
    }
    return clients.youtubeClient.sendMessage(message);
  }
  throw new Error(`${platform} sending is not supported by Lumi.`);
}

function buildTokens({ payload, endpoint, receivedAt }) {
  const data = payload.data || {};
  const currency = (data.currency || "").toString().toUpperCase();
  const priceDisplay = formatMoney(data.price, currency);
  const amountDisplay = formatMoney(data.amount, currency);
  const useAmount = payload.event_type === "contribution_purchased";
  const value = useAmount ? data.amount : data.price;
  const valueDisplay = useAmount ? amountDisplay : priceDisplay;
  const tokens = {
    contract_version: payload.contract_version ?? "",
    event_id: payload.event_id ?? "",
    event_type: payload.event_type ?? "",
    webhook_identifier: endpoint.identifier,
    webhook_uuid: endpoint.uuid,
    received_at: String(receivedAt),
    received_at_iso: new Date(receivedAt).toISOString(),
    creator_id: data.creator_id ?? "",
    creator_username: data.creator_username ?? "",
    gifter_username: data.gifter_username ?? "",
    message: data.message ?? "",
    item_name: data.item_name ?? "",
    item_thumbnail_url: data.item_thumbnail_url ?? "",
    price: data.price ?? "",
    amount: data.amount ?? "",
    currency,
    is_surprise_gift: data.is_surprise_gift ?? "",
    price_display: priceDisplay,
    amount_display: amountDisplay,
    value: value ?? "",
    value_display: valueDisplay,
    payload_json: JSON.stringify(payload)
  };
  for (const [key, value] of Object.entries(data)) {
    tokens[`data.${key}`] = value ?? "";
  }
  return tokens;
}

async function renderTemplate(template, tokens) {
  const rendered = await placeholderService.renderTemplate({
    fieldId: "plugin.throne_wishlist.message_template",
    template,
    outputAudience: "user",
    user: null,
    runtimeContext: {
      runtime: true,
      throneTokens: tokens
    }
  });
  return (rendered.rendered || "").replace(/\{([^{}]+)\}/g, (full, key) => {
    const normalizedKey = String(key || "").trim();
    return Object.prototype.hasOwnProperty.call(tokens, normalizedKey)
      ? String(tokens[normalizedKey])
      : full;
  });
}

function formatMoney(value, currency) {
  const amount = Number(value);
  if (!Number.isFinite(amount)) {
    return "";
  }
  try {
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: currency || "USD"
    }).format(amount / 100);
  } catch {
    return `${(amount / 100).toFixed(2)} ${currency}`.trim();
  }
}

function sanitizePlatformText(value) {
  return (value || "")
    .toString()
    .replace(/@everyone/gi, "@\u200beveryone")
    .replace(/@here/gi, "@\u200bhere")
    .replace(/<@&?\d+>/g, "[mention]")
    .replace(/<#\d+>/g, "[channel]")
    .slice(0, 1800);
}

function isValidEnvelope(payload) {
  return payload &&
    typeof payload.contract_version === "string" &&
    typeof payload.event_id === "string" &&
    payload.event_id.length > 0 &&
    typeof payload.event_type === "string" &&
    payload.event_type.length > 0 &&
    payload.data &&
    typeof payload.data === "object" &&
    !Array.isArray(payload.data);
}

function writePayloadLog({
  endpoint,
  context,
  payload,
  eventId,
  eventType,
  verification,
  authenticityStatus
}) {
  const logEventType = context.jsonError
    ? "invalid"
    : EVENT_TYPES.includes(eventType)
      ? eventType
      : "unknown";
  log("info", `Webhook - Throne - ${logEventType}`, JSON.stringify({
    provider: "throne",
    authentic: verification.authentic,
    authenticity_status: authenticityStatus,
    endpoint_id: endpoint.id,
    endpoint_identifier: endpoint.identifier,
    event_id: eventId,
    event_type: eventType,
    received_at: new Date(context.receivedAt).toISOString(),
    payload: payload || null,
    raw_body_preview: context.jsonError ? context.rawBodyText.slice(0, 4000) : null,
    signature_prefix: (headerValue(context.headers, "x-signature-ed25519") || "").slice(0, 12)
  }));
}

function buildPayloadPreview({ receivedAt, eventType, eventId, authentic, status }) {
  return JSON.stringify({
    received_at: new Date(receivedAt).toISOString(),
    event_type: eventType,
    event_id: eventId,
    authentic,
    status
  });
}

function publishDebug(item) {
  const now = Date.now();
  for (const [id, session] of debugSessions.entries()) {
    if (now - session.createdAt > DEBUG_MAX_MS || now - session.lastPayloadAt > DEBUG_IDLE_MS) {
      closeDebugSession(id, "expired");
      continue;
    }
    session.lastPayloadAt = now;
    clearTimeout(session.idleTimer);
    session.idleTimer = setTimeout(
      () => closeDebugSession(id, "inactivity"),
      DEBUG_IDLE_MS
    );
    if (session.response && !session.response.writableEnded) {
      session.response.write(`event: payload\ndata: ${JSON.stringify(item)}\n\n`);
    }
  }
}

function closeDebugSession(id, reason) {
  const session = debugSessions.get(id);
  if (!session) {
    return;
  }
  clearTimeout(session.expiryTimer);
  clearTimeout(session.idleTimer);
  if (session.response && !session.response.writableEnded) {
    session.response.write(`event: close\ndata: ${JSON.stringify({ reason })}\n\n`);
    session.response.end();
  }
  debugSessions.delete(id);
}

async function buildPlatformStatus({ settings, db, discordClient, twitchClient, youtubeClient }) {
  const configured = new Map(getPlatformStatus().map((entry) => [entry.id, entry]));
  const destinations = new Map(listDestinations(db).map((entry) => [entry.platform, entry]));
  const discordDestination = destinations.get("discord");
  const discordValidation = discordDestination?.destination_id
    ? await validateDiscordChannel(discordClient, discordDestination.destination_id)
    : { valid: false, message: "No Discord channel selected." };
  return PLATFORMS.map((id) => {
    const platform = configured.get(id);
    let sendAvailable = false;
    let diagnostic = "Integration unavailable.";
    if (id === "discord") {
      sendAvailable = Boolean(platform?.enabled && platform?.configured && discordClient);
      diagnostic = sendAvailable ? discordValidation.message : "Discord is disabled, unconfigured, or disconnected.";
    } else if (id === "twitch") {
      sendAvailable = Boolean(platform?.enabled && platform?.configured && twitchClient?.say);
      diagnostic = sendAvailable ? "Twitch send capability available." : "Twitch is disabled, unconfigured, or disconnected.";
    } else if (id === "youtube") {
      sendAvailable = Boolean(platform?.enabled && platform?.configured && youtubeClient?.sendMessage);
      diagnostic = sendAvailable ? "YouTube send capability available." : "YouTube is disabled, unconfigured, or has no active sender.";
    } else {
      diagnostic = "Kick sending is not implemented in Lumi.";
    }
    return {
      id,
      label: platform?.label || id.charAt(0).toUpperCase() + id.slice(1),
      enabled: Boolean(platform?.enabled),
      configured: Boolean(platform?.configured),
      supported: Boolean(platform?.supported),
      sendAvailable,
      diagnostic,
      destinationLabel: id === "discord" && discordValidation.channel
        ? `${discordValidation.channel.guild?.name || "Discord"} - ${discordValidation.channel.name}`
        : null
    };
  });
}

function listEndpoints(db) {
  return db.prepare(
    "SELECT * FROM throne_webhook_endpoints ORDER BY created_at DESC"
  ).all();
}

function getEndpoint(db, id) {
  return db.prepare(
    "SELECT * FROM throne_webhook_endpoints WHERE id = ?"
  ).get(id);
}

function listTemplates(db) {
  return db.prepare(
    "SELECT * FROM throne_event_message_templates ORDER BY event_type, platform"
  ).all();
}

function listDestinations(db) {
  return db.prepare(
    "SELECT * FROM throne_platform_destinations ORDER BY platform"
  ).all();
}

function getPublicBaseUrl(req, settings) {
  const configured = (settings?.getSetting?.("public_base_url", "") || "").toString().trim();
  if (configured) {
    return {
      value: configured.replace(/\/+$/, ""),
      warning: configured.startsWith("https://")
        ? null
        : "Configured public base URL is not HTTPS. Throne production webhooks require HTTPS."
    };
  }
  const forwardedProto = req.get("x-forwarded-proto");
  const protocol = forwardedProto ? forwardedProto.split(",")[0].trim() : req.protocol;
  const value = `${protocol}://${req.get("host")}`;
  return {
    value,
    warning: value.startsWith("https://")
      ? null
      : "No HTTPS public base URL is configured. This URL is best-effort; Throne production webhooks require HTTPS."
  };
}

async function validateDiscordChannel(client, channelId) {
  if (!client) {
    return { valid: false, message: "Discord client is unavailable." };
  }
  if (!channelId) {
    return { valid: false, message: "Select a Discord text channel." };
  }
  const channel = client.channels?.cache?.get(channelId) ||
    (typeof client.channels?.fetch === "function"
      ? await client.channels.fetch(channelId).catch(() => null)
      : null);
  if (!channel || channel.isThread?.() || !(channel.type === "GUILD_TEXT" || channel.type === 0)) {
    return { valid: false, message: "Discord destination must be a regular guild text channel." };
  }
  const member = channel.guild?.members?.cache?.get(client.user?.id) ||
    (typeof channel.guild?.members?.fetch === "function" && client.user?.id
      ? await channel.guild.members.fetch(client.user.id).catch(() => null)
      : null);
  const permissions = member ? channel.permissionsFor(member) : null;
  if (permissions && !hasDiscordPermission(permissions, "VIEW_CHANNEL", "ViewChannel")) {
    return { valid: false, message: "The bot cannot view the selected Discord channel.", channel };
  }
  if (permissions && !hasDiscordPermission(permissions, "SEND_MESSAGES", "SendMessages")) {
    return { valid: false, message: "The bot cannot send messages in the selected Discord channel.", channel };
  }
  return { valid: true, message: "Discord channel is valid.", channel };
}

function hasDiscordPermission(permissions, legacyName, modernName) {
  const value =
    discord.Permissions?.FLAGS?.[legacyName] ||
    discord.PermissionFlagsBits?.[modernName] ||
    legacyName;
  try {
    return Boolean(permissions.has(value));
  } catch {
    return false;
  }
}

function getDiscordChannels(client) {
  const channels = [];
  if (!client?.guilds?.cache) {
    return channels;
  }
  for (const guild of client.guilds.cache.values()) {
    guild.channels?.cache?.forEach((channel) => {
      if (!channel.isThread?.() && (channel.type === "GUILD_TEXT" || channel.type === 0)) {
        channels.push({ id: channel.id, label: `${guild.name} - ${channel.name}` });
      }
    });
  }
  return channels.sort((a, b) => a.label.localeCompare(b.label));
}

function ensureSidebarNavItem(settings) {
  if (!settings?.getSetting || !settings?.setSetting) {
    return;
  }
  const navId = "plugins_throne_wishlist";
  const raw = settings.getSetting("nav_structure", null);
  if (!raw) {
    return;
  }
  let structure = raw;
  if (typeof structure === "string") {
    try {
      structure = JSON.parse(structure);
    } catch {
      return;
    }
  }
  if (!structure?.enabled || !Array.isArray(structure.sections)) {
    return;
  }
  for (const section of structure.sections) {
    if (Array.isArray(section.items)) {
      section.items = section.items.filter((item) => item !== navId);
    }
  }
  let pluginsSection = structure.sections.find((section) => section.id === "plugins");
  if (!pluginsSection) {
    pluginsSection = { id: "plugins", label: "Plugins", icon: "blocks", items: [] };
    structure.sections.push(pluginsSection);
  }
  pluginsSection.items = Array.isArray(pluginsSection.items) ? pluginsSection.items : [];
  pluginsSection.items.push(navId);
  settings.setSetting("nav_structure", structure);
}

function findUnknownPlaceholders(template) {
  const unknown = [];
  for (const match of (template || "").matchAll(/\{\{\s*([^{}]+?)\s*\}\}|\{([^{}]+)\}/g)) {
    const full = match[0];
    const key = normalizeTemplatePlaceholderKey(match[1] || match[2] || "");
    if (!PLACEHOLDERS.has(key) && !unknown.includes(full)) {
      unknown.push(full);
    }
  }
  return unknown;
}

function normalizeTemplatePlaceholderKey(value) {
  const key = String(value || "").trim();
  const prefix = "plugin.throne_wishlist.";
  return key.startsWith(prefix) ? key.slice(prefix.length) : key;
}

function headerValue(headers, name) {
  const value = headers?.[name.toLowerCase()];
  return Array.isArray(value) ? value[0] : (value || "").toString();
}

function normalizeArray(value) {
  if (Array.isArray(value)) {
    return value;
  }
  return value ? [value] : [];
}

function formatTimestamp(value) {
  return value ? new Date(value).toLocaleString() : "Never";
}

function isAdmin(user) {
  return Boolean(user?.isAdmin);
}

function renderDenied(res) {
  return res.status(403).render("error", {
    title: "Access denied",
    message: "Administrator access is required."
  });
}

module.exports.verifyThroneSignature = verifyThroneSignature;
module.exports.handleIncoming = handleIncoming;
module.exports.buildTokens = buildTokens;
module.exports.renderTemplate = renderTemplate;
module.exports.getDiscordChannels = getDiscordChannels;
