import { z } from "zod";
import { createTRPCRouter, controllerProcedure } from "../trpc.js";

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;
};

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,
  };
}

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

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

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

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 } : {}),
  };
}

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 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;

  if (startDate || endDate) {
    const createdAt: Record<string, Date> = {};
    if (startDate) createdAt.gte = startDate;
    if (endDate) createdAt.lte = endDate;
    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> ?? {}), gte: thirtyDaysAgo };
  }

  return where;
}

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 };
}

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

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,
  });
}

async function getAuditTimeline(
  db: { auditLog: { findMany: Function } },
  input: AuditTimelineInput,
) {
  const where: Record<string, unknown> = {};

  if (input.startDate || input.endDate) {
    const createdAt: Record<string, Date> = {};
    if (input.startDate) createdAt.gte = input.startDate;
    if (input.endDate) createdAt.lte = input.endDate;
    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;
}

// ─── Router ───────────────────────────────────────────────────────────────────

export const auditLogRouter = createTRPCRouter({
  /**
   * Paginated, filterable list of audit log entries.
   * Cursor-based pagination using createdAt + id.
   */
  list: controllerProcedure
    .input(
      z.object({
        entityType: z.string().optional(),
        entityId: z.string().optional(),
        userId: z.string().optional(),
        action: z.string().optional(),
        source: z.string().optional(),
        startDate: z.date().optional(),
        endDate: z.date().optional(),
        search: z.string().optional(),
        limit: z.number().min(1).max(100).default(50),
        cursor: z.string().optional(), // id of the last item
      }),
    )
    .query(async ({ ctx, input }) => {
      return listAuditEntries(ctx.db, toAuditListInput({
        entityType: input.entityType,
        entityId: input.entityId,
        userId: input.userId,
        action: input.action,
        source: input.source,
        startDate: input.startDate,
        endDate: input.endDate,
        search: input.search,
        limit: input.limit,
        cursor: input.cursor,
      }));
    }),

  listDetail: controllerProcedure
    .input(
      z.object({
        entityType: z.string().optional(),
        entityId: z.string().optional(),
        userId: z.string().optional(),
        action: z.string().optional(),
        source: z.string().optional(),
        startDate: z.date().optional(),
        endDate: z.date().optional(),
        search: z.string().optional(),
        limit: z.number().min(1).max(100).default(50),
        cursor: z.string().optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const result = await listAuditEntries(ctx.db, toAuditListInput({
        entityType: input.entityType,
        entityId: input.entityId,
        userId: input.userId,
        action: input.action,
        source: input.source,
        startDate: input.startDate,
        endDate: input.endDate,
        search: input.search,
        limit: input.limit,
        cursor: input.cursor,
      }));
      return {
        items: result.items.map(formatAuditListEntry),
        nextCursor: result.nextCursor ?? null,
      };
    }),

  /**
   * Get a single audit entry with full changes JSONB (for expand/detail view).
   */
  getById: controllerProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      return getAuditEntryById(ctx.db, input.id);
    }),

  getByIdDetail: controllerProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const entry = await getAuditEntryById(ctx.db, input.id);
      return formatAuditDetailEntry(entry);
    }),

  /**
   * Get all audit entries for a specific entity (e.g. a project or resource).
   */
  getByEntity: controllerProcedure
    .input(
      z.object({
        entityType: z.string(),
        entityId: z.string(),
        limit: z.number().min(1).max(200).default(50),
      }),
    )
    .query(async ({ ctx, input }) => {
      return getAuditEntriesByEntity(ctx.db, input);
    }),

  getByEntityDetail: controllerProcedure
    .input(
      z.object({
        entityType: z.string(),
        entityId: z.string(),
        limit: z.number().min(1).max(200).default(50),
      }),
    )
    .query(async ({ ctx, input }) => {
      const entries = await getAuditEntriesByEntity(ctx.db, input);
      return {
        entityType: input.entityType,
        entityId: input.entityId,
        entityName: entries[0]?.entityName ?? null,
        itemCount: entries.length,
        items: entries.map(formatAuditDetailEntry),
      };
    }),

  /**
   * Timeline view: entries grouped by date (YYYY-MM-DD).
   */
  getTimeline: controllerProcedure
    .input(
      z.object({
        startDate: z.date().optional(),
        endDate: z.date().optional(),
        limit: z.number().min(1).max(500).default(200),
      }),
    )
    .query(async ({ ctx, input }) => {
      return getAuditTimeline(ctx.db, toAuditTimelineInput({
        startDate: input.startDate,
        endDate: input.endDate,
        limit: input.limit,
      }));
    }),

  getTimelineDetail: controllerProcedure
    .input(
      z.object({
        startDate: z.date().optional(),
        endDate: z.date().optional(),
        limit: z.number().min(1).max(500).default(200),
      }),
    )
    .query(async ({ ctx, input }) => {
      const timeline = await getAuditTimeline(ctx.db, toAuditTimelineInput({
        startDate: input.startDate,
        endDate: input.endDate,
        limit: input.limit,
      }));
      return Object.fromEntries(
        Object.entries(timeline).map(([dateKey, entries]) => [
          dateKey,
          entries.map(formatAuditDetailEntry),
        ]),
      );
    }),

  /**
   * Activity summary: counts by entity type, action, and user for a date range.
   */
  getActivitySummary: controllerProcedure
    .input(
      z.object({
        startDate: z.date().optional(),
        endDate: z.date().optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const where: Record<string, unknown> = {};

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

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

      // Convert to simple Record<string, number>
      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;
      }

      // Resolve user names for the top users
      const userIds = byUserRaw
        .map((row) => row.userId)
        .filter((id): id is string => id !== null);

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

      const userMap = new Map(users.map((u) => [u.id, u.name ?? u.email]));

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

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