type AuditUser = { id: string; name: string | null; email: string | null } | null | undefined;

type AuditEntryShape = {
  id: string;
  entityType: string;
  entityId: string;
  entityName?: string | null;
  action: string;
  userId?: string | null;
  source?: string | null;
  summary?: string | null;
  createdAt: Date;
  user?: AuditUser;
};

type AuditDetailEntryShape = AuditEntryShape & {
  changes?: unknown;
};

export type AuditListInput = {
  entityType?: string;
  entityId?: string;
  userId?: string;
  action?: string;
  source?: string;
  startDate?: Date;
  endDate?: Date;
  search?: string;
  limit: number;
  cursor?: string;
};

export type AuditTimelineInput = {
  startDate?: Date;
  endDate?: Date;
  limit: number;
};

export function formatAuditListEntry(entry: AuditEntryShape) {
  return {
    id: entry.id,
    entityType: entry.entityType,
    entityId: entry.entityId,
    entityName: entry.entityName ?? null,
    action: entry.action,
    userId: entry.userId ?? null,
    source: entry.source ?? null,
    summary: entry.summary ?? null,
    createdAt: entry.createdAt.toISOString(),
    user: entry.user
      ? {
          id: entry.user.id,
          name: entry.user.name,
          email: entry.user.email,
        }
      : null,
  };
}

export function formatAuditDetailEntry(entry: AuditDetailEntryShape) {
  return {
    ...formatAuditListEntry(entry),
    changes: entry.changes ?? null,
  };
}

export function toAuditListInput(input: {
  entityType?: string | undefined;
  entityId?: string | undefined;
  userId?: string | undefined;
  action?: string | undefined;
  source?: string | undefined;
  startDate?: Date | undefined;
  endDate?: Date | undefined;
  search?: string | undefined;
  limit: number;
  cursor?: string | undefined;
}): AuditListInput {
  return {
    limit: input.limit,
    ...(input.entityType !== undefined ? { entityType: input.entityType } : {}),
    ...(input.entityId !== undefined ? { entityId: input.entityId } : {}),
    ...(input.userId !== undefined ? { userId: input.userId } : {}),
    ...(input.action !== undefined ? { action: input.action } : {}),
    ...(input.source !== undefined ? { source: input.source } : {}),
    ...(input.startDate !== undefined ? { startDate: input.startDate } : {}),
    ...(input.endDate !== undefined ? { endDate: input.endDate } : {}),
    ...(input.search !== undefined ? { search: input.search } : {}),
    ...(input.cursor !== undefined ? { cursor: input.cursor } : {}),
  };
}

export function toAuditTimelineInput(input: {
  startDate?: Date | undefined;
  endDate?: Date | undefined;
  limit: number;
}): AuditTimelineInput {
  return {
    limit: input.limit,
    ...(input.startDate !== undefined ? { startDate: input.startDate } : {}),
    ...(input.endDate !== undefined ? { endDate: input.endDate } : {}),
  };
}

function buildCreatedAtWhere(startDate?: Date, endDate?: Date) {
  if (!startDate && !endDate) {
    return undefined;
  }

  const createdAt: Record<string, Date> = {};
  if (startDate) createdAt.gte = startDate;
  if (endDate) createdAt.lte = endDate;
  return createdAt;
}

function buildAuditListWhere(input: Omit<AuditListInput, "limit" | "cursor">) {
  const { entityType, entityId, userId, action, source, startDate, endDate, search } = input;
  const where: Record<string, unknown> = {};

  if (entityType) where.entityType = entityType;
  if (entityId) where.entityId = entityId;
  if (userId) where.userId = userId;
  if (action) where.action = action;
  if (source) where.source = source;

  const createdAt = buildCreatedAtWhere(startDate, endDate);
  if (createdAt) {
    where.createdAt = createdAt;
  }

  if (search) {
    where.OR = [
      { entityName: { contains: search, mode: "insensitive" } },
      { summary: { contains: search, mode: "insensitive" } },
      { entityType: { contains: search, mode: "insensitive" } },
    ];
  }

  if (!startDate && !endDate && !entityId) {
    const thirtyDaysAgo = new Date();
    thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
    where.createdAt = { ...(where.createdAt as Record<string, Date> | undefined), gte: thirtyDaysAgo };
  }

  return where;
}

export async function listAuditEntries(
  db: { auditLog: { findMany: Function } },
  input: AuditListInput,
) {
  const items = await db.auditLog.findMany({
    where: buildAuditListWhere(input),
    select: {
      id: true,
      entityType: true,
      entityId: true,
      entityName: true,
      action: true,
      userId: true,
      source: true,
      summary: true,
      createdAt: true,
      user: { select: { id: true, name: true, email: true } },
    },
    orderBy: { createdAt: "desc" },
    take: input.limit + 1,
    ...(input.cursor ? { cursor: { id: input.cursor }, skip: 1 } : {}),
  });

  let nextCursor: string | undefined;
  if (items.length > input.limit) {
    const next = items.pop();
    nextCursor = next?.id;
  }

  return { items, nextCursor };
}

export async function getAuditEntryById(
  db: { auditLog: { findUniqueOrThrow: Function } },
  id: string,
) {
  return db.auditLog.findUniqueOrThrow({
    where: { id },
    include: { user: { select: { id: true, name: true, email: true } } },
  });
}

export async function getAuditEntriesByEntity(
  db: { auditLog: { findMany: Function } },
  input: { entityType: string; entityId: string; limit: number },
) {
  return db.auditLog.findMany({
    where: {
      entityType: input.entityType,
      entityId: input.entityId,
    },
    include: {
      user: { select: { id: true, name: true, email: true } },
    },
    orderBy: { createdAt: "desc" },
    take: input.limit,
  });
}

export async function getAuditTimeline(
  db: { auditLog: { findMany: Function } },
  input: AuditTimelineInput,
) {
  const where: Record<string, unknown> = {};
  const createdAt = buildCreatedAtWhere(input.startDate, input.endDate);
  if (createdAt) {
    where.createdAt = createdAt;
  }

  const entries = await db.auditLog.findMany({
    where,
    include: {
      user: { select: { id: true, name: true, email: true } },
    },
    orderBy: { createdAt: "desc" },
    take: input.limit,
  });

  const grouped: Record<string, typeof entries> = {};
  for (const entry of entries) {
    const dateKey = entry.createdAt.toISOString().slice(0, 10);
    if (!grouped[dateKey]) grouped[dateKey] = [];
    grouped[dateKey].push(entry);
  }

  return grouped;
}

export async function getAuditActivitySummary(
  db: {
    auditLog: { groupBy: Function; count: Function };
    user: { findMany: Function };
  },
  input: {
    startDate?: Date | undefined;
    endDate?: Date | undefined;
  },
) {
  const where: Record<string, unknown> = {};
  const createdAt = buildCreatedAtWhere(input.startDate, input.endDate);
  if (createdAt) {
    where.createdAt = createdAt;
  }

  const [byEntityTypeRaw, byActionRaw, byUserRaw, total] = await Promise.all([
    db.auditLog.groupBy({
      by: ["entityType"],
      where,
      _count: { id: true },
    }),
    db.auditLog.groupBy({
      by: ["action"],
      where,
      _count: { id: true },
    }),
    db.auditLog.groupBy({
      by: ["userId"],
      where,
      _count: { id: true },
      orderBy: { _count: { id: "desc" } },
      take: 20,
    }),
    db.auditLog.count({ where }),
  ]);

  const byEntityType: Record<string, number> = {};
  for (const row of byEntityTypeRaw) {
    byEntityType[row.entityType] = row._count.id;
  }

  const byAction: Record<string, number> = {};
  for (const row of byActionRaw) {
    byAction[row.action] = row._count.id;
  }

  const userIds = byUserRaw
    .map((row: { userId: string | null }) => row.userId)
    .filter((id: string | null): id is string => id !== null);

  const users = userIds.length > 0
    ? await db.user.findMany({
        where: { id: { in: userIds } },
        select: { id: true, name: true, email: true },
      })
    : [];

  const userMap = new Map(users.map((user: { id: string; name: string | null; email: string | null }) => [
    user.id,
    user.name ?? user.email,
  ]));

  const byUser = byUserRaw
    .filter((row: { userId: string | null }) => row.userId !== null)
    .map((row: { userId: string | null; _count: { id: number } }) => ({
      name: userMap.get(row.userId!) ?? "Unknown",
      count: row._count.id,
    }));

  return { byEntityType, byAction, byUser, total };
}
