// Web Push dispatcher. Fans out a notification to every PushSubscription
// row matching the recipient (owner / admin-as-approver / approver). Runs
// alongside the existing mobile FCM dispatcher in `./push` — same call site
// in recordNotification, separate code path because the wire format differs.
//
// Stale subscriptions (410 Gone) are deleted on the fly so we don't keep
// pushing into dead browser endpoints forever.

import webpush from 'web-push';
import { prisma } from '../config/database';
import type { RecipientType } from './events';

let configured = false;
function ensureConfigured(): boolean {
  if (configured) return true;
  const pub = process.env.VAPID_PUBLIC_KEY;
  const priv = process.env.VAPID_PRIVATE_KEY;
  const subject = process.env.VAPID_SUBJECT || 'mailto:noreply@example.com';
  if (!pub || !priv) {
    console.warn('[webpush] VAPID keys missing — Web Push disabled');
    return false;
  }
  webpush.setVapidDetails(subject, pub, priv);
  configured = true;
  return true;
}

interface WebPushPayload {
  title: string;
  body?: string;
  data?: Record<string, string>;
  link?: string | null;
}

export async function sendWebPushToRecipient(
  recipient: { type: RecipientType; id: string },
  payload: WebPushPayload,
): Promise<void> {
  if (!ensureConfigured()) return;

  // Map our internal recipient type to the column on PushSubscription. Owner
  // sessions store ownerId only; admin sessions store both ownerId + adminId
  // (admin acts as approver in this codebase). Approver sessions still use
  // approverId for the legacy Approver table.
  // Owners subscribe from an owner-only session (no adminId). Admins-as-
  // approvers subscribe with adminId set, and that's also the value the
  // approver-room emitter uses as the id. Pure approvers (legacy Approver
  // table) currently don't have a Web Push login surface — they share the
  // admin path now.
  // CHECKPOINT tokens are stored with approverId = checkpointId (same
  // pattern as push.ts — no extra schema column needed).
  const where: any =
    recipient.type === 'OWNER'      ? { ownerId: recipient.id, adminId: null, approverId: null } :
    recipient.type === 'APPROVER'   ? { OR: [{ adminId: recipient.id }, { approverId: recipient.id }] } :
    recipient.type === 'CHECKPOINT' ? { approverId: recipient.id } :
    null;
  if (!where) return;

  const subs = await prisma.pushSubscription.findMany({ where });
  if (subs.length === 0) return;

  // Payload is JSON-stringified so the SW can parse it directly. Keep it small
  // — Web Push limits payload to ~4KB after encryption; we're well under that.
  const message = JSON.stringify({
    title: payload.title,
    body: payload.body || '',
    data: payload.data || {},
    link: payload.link || null,
  });

  const deadEndpoints: string[] = [];
  await Promise.allSettled(subs.map(async (sub) => {
    try {
      await webpush.sendNotification(
        { endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } },
        message,
      );
    } catch (e: any) {
      const code = e?.statusCode;
      // 404 + 410 = subscription revoked / endpoint gone → delete the row so
      // we stop pushing into it. Any other error is logged but kept around
      // (might be a transient network blip, the browser's push server, etc.).
      if (code === 404 || code === 410) {
        deadEndpoints.push(sub.endpoint);
      } else {
        console.error(`[webpush] send failed (status=${code})`, e?.body || e?.message || e);
      }
    }
  }));

  if (deadEndpoints.length > 0) {
    await prisma.pushSubscription.deleteMany({ where: { endpoint: { in: deadEndpoints } } });
    console.log(`[webpush] pruned ${deadEndpoints.length} dead subscription(s)`);
  }
}

// Read-only accessor for the controller that hands the VAPID public key to
// the browser. Never exposes the private key.
export function getVapidPublicKey(): string | null {
  return process.env.VAPID_PUBLIC_KEY || null;
}
