import { and, count, desc, eq, inArray, isNull } from 'drizzle-orm';
import { appNotifications } from '@/db/schema';
import { auditAction } from '@/features/foundation/audit-log/service';
import { db } from '@/lib/db';
import type { AppNotificationRecord, NotificationListFilters } from '../types';

function mapNotificationRecord(row: typeof appNotifications.$inferSelect): AppNotificationRecord {
  return {
    id: row.id,
    organizationId: row.organizationId,
    recipientUserId: row.recipientUserId,
    eventId: row.eventId,
    title: row.title,
    body: row.body,
    linkUrl: row.linkUrl ?? null,
    severity: row.severity as AppNotificationRecord['severity'],
    status: row.status as AppNotificationRecord['status'],
    readAt: row.readAt?.toISOString() ?? null,
    archivedAt: row.archivedAt?.toISOString() ?? null,
    metadata: (row.metadata as Record<string, unknown> | null) ?? null,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString()
  };
}

export async function listNotifications(
  organizationId: string,
  userId: string,
  filters: NotificationListFilters
) {
  const page = Math.max(filters.page ?? 1, 1);
  const limit = Math.min(Math.max(filters.limit ?? 20, 1), 100);
  const where = and(
    eq(appNotifications.organizationId, organizationId),
    eq(appNotifications.recipientUserId, userId),
    ...(filters.status && filters.status !== 'all'
      ? [
          filters.status === 'archived'
            ? eq(appNotifications.status, 'archived')
            : and(eq(appNotifications.status, filters.status), isNull(appNotifications.archivedAt))!
        ]
      : [])
  );

  const [rows, totalRow, unreadRow] = await Promise.all([
    db
      .select()
      .from(appNotifications)
      .where(where)
      .orderBy(desc(appNotifications.createdAt))
      .limit(limit)
      .offset((page - 1) * limit),
    db.select({ value: count() }).from(appNotifications).where(where),
    db
      .select({ value: count() })
      .from(appNotifications)
      .where(
        and(
          eq(appNotifications.organizationId, organizationId),
          eq(appNotifications.recipientUserId, userId),
          eq(appNotifications.status, 'unread'),
          isNull(appNotifications.archivedAt)
        )
      )
  ]);

  return {
    items: rows.map(mapNotificationRecord),
    totalItems: totalRow[0]?.value ?? 0,
    unreadCount: unreadRow[0]?.value ?? 0,
    page,
    limit
  };
}

export async function getUnreadNotificationCount(organizationId: string, userId: string) {
  const [row] = await db
    .select({ value: count() })
    .from(appNotifications)
    .where(
      and(
        eq(appNotifications.organizationId, organizationId),
        eq(appNotifications.recipientUserId, userId),
        eq(appNotifications.status, 'unread'),
        isNull(appNotifications.archivedAt)
      )
    );

  return row?.value ?? 0;
}

export async function markNotificationAsRead(
  organizationId: string,
  userId: string,
  notificationId: string
) {
  const [current] = await db
    .select()
    .from(appNotifications)
    .where(
      and(
        eq(appNotifications.id, notificationId),
        eq(appNotifications.organizationId, organizationId),
        eq(appNotifications.recipientUserId, userId)
      )
    )
    .limit(1);

  if (!current) {
    return null;
  }

  if (current.status === 'read') {
    return mapNotificationRecord(current);
  }

  const [updated] = await db
    .update(appNotifications)
    .set({
      status: 'read',
      readAt: current.readAt ?? new Date(),
      updatedAt: new Date()
    })
    .where(eq(appNotifications.id, notificationId))
    .returning();

  await auditAction({
    organizationId,
    userId,
    entityType: 'app_notification',
    entityId: notificationId,
    action: 'notification_read',
    beforeData: current,
    afterData: updated
  });

  return mapNotificationRecord(updated);
}

export async function markAllNotificationsAsRead(organizationId: string, userId: string) {
  const rows = await db
    .select({ id: appNotifications.id })
    .from(appNotifications)
    .where(
      and(
        eq(appNotifications.organizationId, organizationId),
        eq(appNotifications.recipientUserId, userId),
        eq(appNotifications.status, 'unread'),
        isNull(appNotifications.archivedAt)
      )
    );

  const ids = rows.map((row) => row.id);
  if (!ids.length) {
    return 0;
  }

  await db
    .update(appNotifications)
    .set({
      status: 'read',
      readAt: new Date(),
      updatedAt: new Date()
    })
    .where(inArray(appNotifications.id, ids));

  await auditAction({
    organizationId,
    userId,
    entityType: 'app_notification',
    entityId: `bulk:${ids.length}`,
    action: 'notification_read_all',
    afterData: { notificationIds: ids }
  });

  return ids.length;
}

export async function archiveNotification(
  organizationId: string,
  userId: string,
  notificationId: string
) {
  const [current] = await db
    .select()
    .from(appNotifications)
    .where(
      and(
        eq(appNotifications.id, notificationId),
        eq(appNotifications.organizationId, organizationId),
        eq(appNotifications.recipientUserId, userId)
      )
    )
    .limit(1);

  if (!current) {
    return null;
  }

  const [updated] = await db
    .update(appNotifications)
    .set({
      status: 'archived',
      archivedAt: current.archivedAt ?? new Date(),
      readAt: current.readAt ?? new Date(),
      updatedAt: new Date()
    })
    .where(eq(appNotifications.id, notificationId))
    .returning();

  await auditAction({
    organizationId,
    userId,
    entityType: 'app_notification',
    entityId: notificationId,
    action: 'notification_archived',
    beforeData: current,
    afterData: updated
  });

  return mapNotificationRecord(updated);
}
