/**
 * In-app notification service.
 *
 * Stores lightweight notifications in KeyValueStore (namespace "app-notifications").
 * Used for sync completion alerts, errors, etc.
 */

import { prisma } from "@/core/storage/prisma";
import type { Prisma } from "@prisma/client";

/* ------------------------------------------------------------------ */
/*  Types                                                              */
/* ------------------------------------------------------------------ */

export type AppNotificationType = "sync-complete" | "sync-error";

export interface AppNotification {
  id: string;
  type: AppNotificationType;
  title: string;
  message: string;
  createdAt: string;
  readAt: string | null;
  metadata?: Record<string, unknown>;
}

const NAMESPACE = "app-notifications";
const MAX_AGE_DAYS = 30;

/* ------------------------------------------------------------------ */
/*  Create                                                             */
/* ------------------------------------------------------------------ */

export async function createAppNotification(
  input: Omit<AppNotification, "id" | "createdAt" | "readAt">,
): Promise<AppNotification> {
  const notification: AppNotification = {
    id: crypto.randomUUID(),
    createdAt: new Date().toISOString(),
    readAt: null,
    ...input,
  };

  await prisma.keyValueStore.create({
    data: {
      namespace: NAMESPACE,
      key: notification.id,
      value: notification as unknown as Prisma.InputJsonValue,
    },
  });

  return notification;
}

/* ------------------------------------------------------------------ */
/*  Read                                                               */
/* ------------------------------------------------------------------ */

export async function getAppNotifications(limit = 30): Promise<AppNotification[]> {
  const rows = await prisma.keyValueStore.findMany({
    where: { namespace: NAMESPACE },
    orderBy: { createdAt: "desc" },
    take: limit,
  });

  const cutoff = Date.now() - MAX_AGE_DAYS * 86_400_000;
  const notifications: AppNotification[] = [];
  const staleIds: string[] = [];

  for (const row of rows) {
    const n = row.value as unknown as AppNotification;
    if (new Date(n.createdAt).getTime() < cutoff) {
      staleIds.push(row.id);
    } else {
      notifications.push(n);
    }
  }

  // Lazy cleanup of old notifications
  if (staleIds.length > 0) {
    void prisma.keyValueStore.deleteMany({
      where: { id: { in: staleIds } },
    });
  }

  return notifications;
}

export async function getUnreadCount(): Promise<number> {
  const rows = await prisma.$queryRaw<Array<{ count: number }>>`
    SELECT COUNT(*)::int as count
    FROM "KeyValueStore"
    WHERE namespace = ${NAMESPACE}
      AND value->>'readAt' IS NULL
  `;
  return rows[0]?.count ?? 0;
}

/* ------------------------------------------------------------------ */
/*  Update                                                             */
/* ------------------------------------------------------------------ */

export async function markAsRead(id: string): Promise<void> {
  const row = await prisma.keyValueStore.findUnique({
    where: { namespace_key: { namespace: NAMESPACE, key: id } },
  });
  if (!row) return;

  const n = row.value as unknown as AppNotification;
  n.readAt = new Date().toISOString();

  await prisma.keyValueStore.update({
    where: { namespace_key: { namespace: NAMESPACE, key: id } },
    data: { value: n as unknown as Prisma.InputJsonValue },
  });
}

export async function markAllAsRead(): Promise<void> {
  const rows = await prisma.keyValueStore.findMany({
    where: { namespace: NAMESPACE },
  });

  const now = new Date().toISOString();
  const updates = rows
    .filter((r) => {
      const n = r.value as unknown as AppNotification;
      return n.readAt === null;
    })
    .map((r) => {
      const n = r.value as unknown as AppNotification;
      n.readAt = now;
      return prisma.keyValueStore.update({
        where: { namespace_key: { namespace: NAMESPACE, key: r.key } },
        data: { value: n as unknown as Prisma.InputJsonValue },
      });
    });

  if (updates.length > 0) {
    await prisma.$transaction(updates);
  }
}
