import { createDbService } from '@/server/services/db.js';
import { z } from 'zod';
import { getNotificationById } from '../../../db/queries/live-notifications.js';
import { sendPushFallback } from '../../../notifications/push-fallback.js';
import type { NotifEvent } from '../../../notifications/event-registry.js';
import type { OutboxHandler } from '../types.js';

const payloadSchema = z
  .object({
    notificationId: z.uuid(),
    userId: z.uuid(),
  })
  .loose();

type Payload = z.infer<typeof payloadSchema>;

export const notificationDeliver: OutboxHandler<Payload> = {
  type: 'notification.deliver',
  payloadSchema,
  async handle(ctx, payload, event) {
    const db = createDbService({ DATABASE_URL: ctx.DATABASE_URL });

    // Load live_notifications row
    const notif = await getNotificationById(db, payload.notificationId);

    if (!notif) {
      // Row gone (TTL purge or manual delete) — no-op, ack
      console.warn(
        JSON.stringify({
          event: 'notif_deliver_row_missing',
          notificationId: payload.notificationId,
          outboxId: event.id,
        }),
      );
      return;
    }

    // Skip if already read or expired
    if (notif.readAt || notif.expiresAt < new Date()) {
      return;
    }

    // Try live delivery via UserSessionDO
    let hasLiveSockets = false;
    if (ctx.USER_SESSION_DO) {
      const userStub = ctx.USER_SESSION_DO.get(
        ctx.USER_SESSION_DO.idFromName(`user:${payload.userId}`),
      ) as unknown as {
        deliver(envelope: {
          ch: 'notif';
          kind: 'inbox';
          tier: 'critical' | 'optional' | 'marketing';
          notif: { id: string; event: string; titleHe: string; titleEn: string };
          ts: number;
        }): Promise<void>;
        hasOpenSockets(): Promise<boolean>;
      };

      await userStub.deliver({
        ch: 'notif',
        kind: 'inbox',
        tier: notif.tier as 'critical' | 'optional' | 'marketing',
        notif: {
          id: notif.id,
          event: notif.event,
          titleHe: notif.titleHe,
          titleEn: notif.titleEn,
        },
        ts: Date.now(),
      });

      hasLiveSockets = await userStub.hasOpenSockets();
    }

    // Push fallback when user has no open WS connections
    if (!hasLiveSockets) {
      if (!ctx.VAPID_PUBLIC_KEY || !ctx.VAPID_PRIVATE_KEY || !ctx.VAPID_SUBJECT) {
        if (ctx.strict !== false) {
          throw new Error(
            '[outbox] VAPID keys not configured for notification.deliver push fallback',
          );
        }
        console.warn(
          JSON.stringify({
            event: 'outbox_secret_missing',
            secret: 'VAPID_KEYS',
            outboxId: event.id,
          }),
        );
        return;
      }

      await sendPushFallback({
        userId: payload.userId,
        event: notif.event as NotifEvent,
        title: { he: notif.titleHe, en: notif.titleEn },
        body: { he: notif.bodyHe ?? null, en: notif.bodyEn ?? null },
        link: notif.link ?? undefined,
        db,
        env: {
          VAPID_PUBLIC_KEY: ctx.VAPID_PUBLIC_KEY,
          VAPID_PRIVATE_KEY: ctx.VAPID_PRIVATE_KEY,
          VAPID_SUBJECT: ctx.VAPID_SUBJECT,
          DATABASE_URL: ctx.DATABASE_URL,
        },
      });
    }
  },
};
