/**
 * Notification Service — Core notification dispatch + WebSocket delivery.
 *
 * Modules call `notify()` to persist a notification to PostgreSQL and
 * attempt real-time delivery via WebSocket. The WS registry tracks
 * which user DIDs have active connections.
 */

import type { ServerWebSocket } from "bun";
import webpush from "web-push";
import {
  createNotification,
  getUnreadCount,
  markNotificationDelivered,
  listSpaceMembers,
  getUserPushSubscriptions,
  deletePushSubscriptionByEndpoint,
  updatePushSubscriptionLastUsed,
  getNotificationPreferences,
  getProfileEmailsByDids,
  type StoredNotification,
} from "../src/encryptid/db";

// ── VAPID setup ──
const VAPID_PUBLIC_KEY = process.env.VAPID_PUBLIC_KEY;
const VAPID_PRIVATE_KEY = process.env.VAPID_PRIVATE_KEY;
const VAPID_SUBJECT = process.env.VAPID_SUBJECT || "mailto:admin@rspace.online";

if (VAPID_PUBLIC_KEY && VAPID_PRIVATE_KEY) {
  webpush.setVapidDetails(VAPID_SUBJECT, VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY);
  console.log("[push] VAPID configured");
} else {
  console.warn("[push] VAPID keys not set — Web Push disabled");
}

// ── SMTP setup (email delivery) ──
const SMTP_HOST = process.env.SMTP_HOST || "mail.rmail.online";
const SMTP_PORT = Number(process.env.SMTP_PORT) || 587;
const SMTP_USER = process.env.SMTP_USER || "";
const SMTP_PASS = process.env.SMTP_PASS || "";

let _smtpTransport: any = null;

export async function getSmtpTransport() {
  if (_smtpTransport) return _smtpTransport;
  const isInternal = SMTP_HOST.includes('mailcow') || SMTP_HOST.includes('postfix');
  if (!SMTP_PASS && !isInternal) return null;
  try {
    const nodemailer = await import("nodemailer");
    const createTransport = (nodemailer as any).default?.createTransport || nodemailer.createTransport;
    _smtpTransport = createTransport({
      host: SMTP_HOST,
      port: isInternal ? 25 : SMTP_PORT,
      secure: !isInternal && SMTP_PORT === 465,
      ...(isInternal ? {} : SMTP_USER ? { auth: { user: SMTP_USER, pass: SMTP_PASS } } : {}),
      tls: { rejectUnauthorized: false },
    });
    console.log("[email] SMTP transport configured");
    return _smtpTransport;
  } catch (e) {
    console.warn("[email] Failed to create SMTP transport:", e);
    return null;
  }
}

// ============================================================================
// TYPES
// ============================================================================

export type NotificationCategory = 'space' | 'module' | 'system' | 'social' | 'payment';

export type NotificationEventType =
  // Space
  | 'access_request' | 'access_approved' | 'access_denied'
  | 'member_joined' | 'member_left' | 'role_changed'
  | 'nest_request' | 'nest_created' | 'space_invite'
  // Module
  | 'inbox_new_mail' | 'inbox_approval_needed' | 'choices_result'
  | 'notes_shared' | 'canvas_mention' | 'canvas_comment'
  | 'module_comment' | 'module_mention'
  // System
  | 'guardian_invite' | 'guardian_accepted' | 'recovery_initiated'
  | 'recovery_approved' | 'device_linked' | 'security_alert'
  // Social
  | 'mention' | 'ping_user'
  // Delegation
  | 'delegation_received' | 'delegation_revoked' | 'delegation_expired'
  // Commitment (rTime)
  | 'commitment_requested' | 'commitment_accepted' | 'commitment_declined'
  // Payment
  | 'payment_sent' | 'payment_received' | 'payment_request_fulfilled'
  // Chat
  | 'chat_message' | 'chat_mention' | 'chat_dm';

export interface NotifyOptions {
  userDid: string;
  category: NotificationCategory;
  eventType: NotificationEventType;
  title: string;
  body?: string;
  spaceSlug?: string;
  moduleId?: string;
  actionUrl?: string;
  actorDid?: string;
  actorUsername?: string;
  metadata?: Record<string, any>;
  expiresAt?: Date;
}

// ============================================================================
// WS CONNECTION REGISTRY
// ============================================================================

const userConnections = new Map<string, Set<ServerWebSocket<any>>>();

export function registerUserConnection(userDid: string, ws: ServerWebSocket<any>): void {
  let conns = userConnections.get(userDid);
  if (!conns) {
    conns = new Set();
    userConnections.set(userDid, conns);
  }
  conns.add(ws);
}

export function unregisterUserConnection(userDid: string, ws: ServerWebSocket<any>): void {
  const conns = userConnections.get(userDid);
  if (!conns) return;
  conns.delete(ws);
  if (conns.size === 0) userConnections.delete(userDid);
}

// ============================================================================
// CORE DISPATCH
// ============================================================================

export async function notify(opts: NotifyOptions): Promise<StoredNotification> {
  const id = crypto.randomUUID();

  // 1. Persist to DB
  const stored = await createNotification({
    id,
    userDid: opts.userDid,
    category: opts.category,
    eventType: opts.eventType,
    title: opts.title,
    body: opts.body,
    spaceSlug: opts.spaceSlug,
    moduleId: opts.moduleId,
    actionUrl: opts.actionUrl,
    actorDid: opts.actorDid,
    actorUsername: opts.actorUsername,
    metadata: opts.metadata,
    expiresAt: opts.expiresAt,
  });

  // 2. Attempt WS delivery
  const conns = userConnections.get(opts.userDid);
  if (conns && conns.size > 0) {
    const unreadCount = await getUnreadCount(opts.userDid);
    const payload = JSON.stringify({
      type: "notification",
      notification: {
        id: stored.id,
        category: stored.category,
        eventType: stored.eventType,
        title: stored.title,
        body: stored.body,
        spaceSlug: stored.spaceSlug,
        actorUsername: stored.actorUsername,
        actionUrl: stored.actionUrl,
        metadata: stored.metadata,
        createdAt: stored.createdAt,
      },
      unreadCount,
    });

    let delivered = false;
    for (const ws of conns) {
      try {
        if (ws.readyState === WebSocket.OPEN) {
          ws.send(payload);
          delivered = true;
        }
      } catch {
        // Connection may have closed between check and send
      }
    }

    if (delivered) {
      await markNotificationDelivered(stored.id, 'ws');
    }
  }

  // 3. Attempt Web Push delivery (non-blocking)
  if (VAPID_PUBLIC_KEY && VAPID_PRIVATE_KEY) {
    sendWebPush(stored, opts).catch(() => {});
  }

  // 4. Attempt email delivery (non-blocking)
  sendEmailNotification(stored, opts).catch(() => {});

  return stored;
}

/** Send Web Push to all of a user's subscriptions. */
async function sendWebPush(stored: StoredNotification, opts: NotifyOptions): Promise<void> {
  // Check if user has push enabled
  const prefs = await getNotificationPreferences(opts.userDid);
  if (prefs && !prefs.pushEnabled) return;

  const subs = await getUserPushSubscriptions(opts.userDid);
  if (subs.length === 0) return;

  const pushPayload = JSON.stringify({
    title: stored.title,
    body: stored.body || "",
    icon: "/icons/icon-192.png",
    badge: "/icons/icon-192.png",
    tag: `${stored.category}-${stored.eventType}`,
    data: {
      url: stored.actionUrl || "/",
      notificationId: stored.id,
    },
  });

  let anyDelivered = false;
  await Promise.allSettled(
    subs.map(async (sub) => {
      try {
        await webpush.sendNotification(
          {
            endpoint: sub.endpoint,
            keys: { p256dh: sub.keyP256dh, auth: sub.keyAuth },
          },
          pushPayload,
        );
        anyDelivered = true;
        updatePushSubscriptionLastUsed(sub.id).catch(() => {});
      } catch (err: any) {
        // 404/410 = subscription expired, clean up
        if (err?.statusCode === 404 || err?.statusCode === 410) {
          await deletePushSubscriptionByEndpoint(sub.endpoint);
        }
      }
    }),
  );

  if (anyDelivered) {
    await markNotificationDelivered(stored.id, 'push');
  }
}

/** Send email notification from {space}-agent@rspace.online. */
async function sendEmailNotification(stored: StoredNotification, opts: NotifyOptions): Promise<void> {
  // Check if user has email enabled
  const prefs = await getNotificationPreferences(opts.userDid);
  if (prefs && !prefs.emailEnabled) return;

  // Look up user's email
  const emailMap = await getProfileEmailsByDids([opts.userDid]);
  const userEmail = emailMap.get(opts.userDid);
  if (!userEmail) return;

  const transport = await getSmtpTransport();
  if (!transport) return;

  const space = opts.spaceSlug || "rspace";
  const fromAddr = `${space} agent <${space}-agent@rspace.online>`;
  const actionLink = opts.actionUrl
    ? `https://${space}.rspace.online${opts.actionUrl}`
    : `https://${space}.rspace.online`;

  const html = `
    <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 480px; margin: 0 auto; padding: 24px;">
      <div style="background: #1e293b; border-radius: 10px; padding: 20px; color: #e2e8f0;">
        <h2 style="margin: 0 0 8px; font-size: 16px; color: #f1f5f9;">${escapeHtml(stored.title)}</h2>
        ${stored.body ? `<p style="margin: 0 0 16px; font-size: 14px; color: #94a3b8;">${escapeHtml(stored.body)}</p>` : ""}
        <a href="${actionLink}" style="display: inline-block; padding: 8px 20px; background: linear-gradient(135deg, #14b8a6, #0d9488); color: white; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 600;">View in rSpace</a>
      </div>
      <p style="margin: 12px 0 0; font-size: 11px; color: #64748b; text-align: center;">
        Sent by ${space}.rspace.online
      </p>
    </div>`;

  try {
    await transport.sendMail({
      from: fromAddr,
      to: userEmail,
      subject: stored.title,
      html,
      replyTo: `${space}-agent@rspace.online`,
    });
    await markNotificationDelivered(stored.id, 'email');
    console.log(`[email] Sent "${stored.title}" to ${opts.userDid}`);
  } catch (err: any) {
    console.error(`[email] Failed to send to ${opts.userDid}:`, err.message);
  }
}

function escapeHtml(s: string): string {
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}

// ============================================================================
// CONVENIENCE: NOTIFY SPACE ADMINS/MODS
// ============================================================================

export async function notifySpaceAdmins(
  spaceSlug: string,
  opts: Omit<NotifyOptions, 'userDid'>,
): Promise<void> {
  const members = await listSpaceMembers(spaceSlug);
  const targets = members.filter(m => m.role === 'admin' || m.role === 'moderator');

  await Promise.all(
    targets.map(m => notify({ ...opts, userDid: m.userDID })),
  );
}
