import { and, eq, inArray } from 'drizzle-orm';
import { notifications } from '@/db/schema';
import {
  AUDIT_ACTION,
  AUDIT_MODULE,
  getRequestMetadata,
  logAuditEvent,
  toAuditValue
} from '@/features/audit-logs/server/audit-service';
import { db } from '@/lib/db';
import type { NotificationType } from '../api/types';

type NotificationRow = typeof notifications.$inferSelect;
type NotificationInsert = typeof notifications.$inferInsert;

function mapNotificationType(type: NotificationType): NotificationInsert['type'] {
  switch (type) {
    case 'CONTENT_APPROVED':
    case 'TRAINING_APPROVED':
    case 'MASTER_REVIEW_APPROVED':
      return 'approved';
    case 'CONTENT_RETURNED':
    case 'CONTENT_REJECTED':
    case 'TRAINING_REJECTED':
    case 'MASTER_REVIEW_REJECTED':
      return 'rejected';
    case 'CONTENT_SUBMITTED':
    case 'CONTENT_PUBLISHED':
    case 'ANNOUNCEMENT_PUBLISHED':
      return 'announcement';
    case 'IMPORT_COMPLETED':
    case 'SYSTEM':
      return 'reminder';
    default:
      return 'reminder';
  }
}

export function resolveNotificationType(row: Pick<NotificationRow, 'type' | 'referenceType'>): NotificationType {
  if (row.referenceType === 'training_record' && row.type === 'approved') return 'TRAINING_APPROVED';
  if (row.referenceType === 'training_record' && row.type === 'rejected') return 'TRAINING_REJECTED';
  if (row.referenceType === 'content_approval' && row.type === 'approved') return 'CONTENT_APPROVED';
  if (row.referenceType === 'content_approval' && row.type === 'rejected') return 'CONTENT_REJECTED';
  if (row.referenceType === 'content_submission' && row.type === 'announcement') return 'CONTENT_SUBMITTED';
  if (row.referenceType === 'content_published' && row.type === 'announcement') return 'CONTENT_PUBLISHED';
  if (row.referenceType === 'announcement' || row.type === 'announcement') return 'ANNOUNCEMENT_PUBLISHED';
  if (row.referenceType === 'import_job') return 'IMPORT_COMPLETED';
  if (row.referenceType === 'master_review' && row.type === 'approved') return 'MASTER_REVIEW_APPROVED';
  if (row.referenceType === 'master_review' && row.type === 'rejected') return 'MASTER_REVIEW_REJECTED';
  return 'SYSTEM';
}

export async function createNotification({
  organizationId,
  userId,
  title,
  message,
  type,
  referenceType,
  referenceId,
  audit = false,
  actorUserId,
  request
}: {
  organizationId: string;
  userId: string;
  title: string;
  message: string;
  type: NotificationType;
  referenceType?: string | null;
  referenceId?: string | number | null;
  audit?: boolean;
  actorUserId?: string | null;
  request?: Request | null;
}) {
  const [created] = await db
    .insert(notifications)
    .values({
      organizationId,
      userId,
      title,
      message,
      type: mapNotificationType(type),
      referenceType: referenceType ?? null,
      referenceId: referenceId === undefined || referenceId === null ? null : String(referenceId)
    })
    .returning();

  if (audit || type === 'SYSTEM') {
    const requestMetadata = request ? getRequestMetadata(request) : {};
    await logAuditEvent({
      organizationId,
      userId: actorUserId ?? userId,
      module: AUDIT_MODULE.NOTIFICATIONS,
      action: AUDIT_ACTION.NOTIFICATION_CREATE,
      entityName: title,
      entityId: created.id,
      newValue: toAuditValue({
        title,
        message,
        type,
        reference_type: referenceType ?? null,
        reference_id: referenceId === undefined || referenceId === null ? null : String(referenceId)
      }),
      ...requestMetadata
    });
  }

  return created;
}

export async function createTrainingApprovedNotification({
  organizationId,
  userId,
  courseName,
  category,
  approvedHours,
  referenceId
}: {
  organizationId: string;
  userId: string;
  courseName: string;
  category: string | null;
  approvedHours: number | null;
  referenceId: string | number;
}) {
  return createNotification({
    organizationId,
    userId,
    title: 'รายการอบรมได้รับการอนุมัติ',
    message: `${courseName}${category ? ` หมวด ${category}` : ''}${approvedHours !== null ? ` ${approvedHours} ชั่วโมง` : ''}`,
    type: 'TRAINING_APPROVED',
    referenceType: 'training_record',
    referenceId
  });
}

export async function createTrainingRejectedNotification({
  organizationId,
  userId,
  courseName,
  rejectReason,
  referenceId
}: {
  organizationId: string;
  userId: string;
  courseName: string;
  rejectReason: string | null;
  referenceId: string | number;
}) {
  return createNotification({
    organizationId,
    userId,
    title: 'รายการอบรมไม่ผ่านการอนุมัติ',
    message: `${courseName}${rejectReason ? ` เหตุผล: ${rejectReason}` : ''}`,
    type: 'TRAINING_REJECTED',
    referenceType: 'training_record',
    referenceId
  });
}

export async function createAnnouncementNotification({
  organizationId,
  userIds,
  title,
  message,
  referenceId
}: {
  organizationId: string;
  userIds: string[];
  title: string;
  message: string;
  referenceId: string | number;
}) {
  if (userIds.length === 0) return;

  await db.insert(notifications).values(
    userIds.map((userId) => ({
      organizationId,
      userId,
      title,
      message,
      type: 'announcement' as const,
      referenceType: 'announcement',
      referenceId: String(referenceId)
    }))
  );
}

export async function createContentWorkflowNotification({
  organizationId,
  userId,
  title,
  message,
  type,
  referenceId,
  referenceType
}: {
  organizationId: string;
  userId: string;
  title: string;
  message: string;
  type: 'CONTENT_SUBMITTED' | 'CONTENT_APPROVED' | 'CONTENT_RETURNED' | 'CONTENT_REJECTED' | 'CONTENT_PUBLISHED';
  referenceId: string | number;
  referenceType: 'content_submission' | 'content_approval' | 'content_published';
}) {
  return createNotification({
    organizationId,
    userId,
    title,
    message,
    type,
    referenceType,
    referenceId
  });
}

export async function createImportCompletedNotification({
  organizationId,
  userId,
  totalRows,
  successRows,
  failedRows,
  referenceId
}: {
  organizationId: string;
  userId: string;
  totalRows: number;
  successRows: number;
  failedRows: number;
  referenceId?: string | number | null;
}) {
  return createNotification({
    organizationId,
    userId,
    title: 'นำเข้าพนักงานเสร็จสิ้น',
    message: `ทั้งหมด ${totalRows} รายการ สำเร็จ ${successRows} รายการ ไม่สำเร็จ ${failedRows} รายการ`,
    type: 'IMPORT_COMPLETED',
    referenceType: 'import_job',
    referenceId: referenceId ?? null
  });
}

export async function createMasterReviewNotification({
  organizationId,
  userId,
  masterType,
  entityName,
  action,
  referenceId
}: {
  organizationId: string;
  userId: string;
  masterType: string;
  entityName: string;
  action: 'approve' | 'reject';
  referenceId?: string | number | null;
}) {
  const approved = action === 'approve';

  return createNotification({
    organizationId,
    userId,
    title: approved ? 'อนุมัติข้อมูลหลักเรียบร้อยแล้ว' : 'ไม่อนุมัติข้อมูลหลักเรียบร้อยแล้ว',
    message: `${masterType} ${entityName} ${approved ? 'ได้รับการอนุมัติ' : 'ไม่ผ่านการอนุมัติ'}`,
    type: approved ? 'MASTER_REVIEW_APPROVED' : 'MASTER_REVIEW_REJECTED',
    referenceType: 'master_review',
    referenceId: referenceId ?? null
  });
}

export async function markNotificationAsRead({
  organizationId,
  notificationId,
  actorUserId,
  request
}: {
  organizationId: string;
  notificationId: number;
  actorUserId: string;
  request: Request;
}) {
  const [existing] = await db
    .select()
    .from(notifications)
    .where(
      and(
        eq(notifications.id, notificationId),
        eq(notifications.organizationId, organizationId),
        eq(notifications.userId, actorUserId)
      )
    )
    .limit(1);

  if (!existing) {
    return null;
  }

  if (existing.isRead) {
    return existing;
  }

  const [updated] = await db
    .update(notifications)
    .set({
      isRead: true,
      readAt: new Date()
    })
    .where(
      and(
        eq(notifications.id, notificationId),
        eq(notifications.organizationId, organizationId),
        eq(notifications.userId, actorUserId)
      )
    )
    .returning();

  await logAuditEvent({
    organizationId,
    userId: actorUserId,
    module: AUDIT_MODULE.NOTIFICATIONS,
    action: AUDIT_ACTION.NOTIFICATION_MARK_READ,
    entityName: updated.title,
    entityId: updated.id,
    oldValue: toAuditValue({ is_read: existing.isRead, read_at: existing.readAt?.toISOString() ?? null }),
    newValue: toAuditValue({ is_read: updated.isRead, read_at: updated.readAt?.toISOString() ?? null }),
    ...getRequestMetadata(request)
  });

  return updated;
}

export async function markAllNotificationsAsRead({
  organizationId,
  actorUserId,
  request
}: {
  organizationId: string;
  actorUserId: string;
  request: Request;
}) {
  const unreadRows = await db
    .select({ id: notifications.id })
    .from(notifications)
    .where(
      and(
        eq(notifications.organizationId, organizationId),
        eq(notifications.userId, actorUserId),
        eq(notifications.isRead, false)
      )
    );

  if (unreadRows.length === 0) {
    return 0;
  }

  const ids = unreadRows.map((row) => row.id);

  await db
    .update(notifications)
    .set({
      isRead: true,
      readAt: new Date()
    })
    .where(
      and(
        eq(notifications.organizationId, organizationId),
        eq(notifications.userId, actorUserId),
        inArray(notifications.id, ids)
      )
    );

  await logAuditEvent({
    organizationId,
    userId: actorUserId,
    module: AUDIT_MODULE.NOTIFICATIONS,
    action: AUDIT_ACTION.NOTIFICATION_MARK_ALL_READ,
    entityName: 'notifications',
    newValue: toAuditValue({ count: ids.length }),
    ...getRequestMetadata(request)
  });

  return ids.length;
}
