import type { PrismaClient, AuditEvent, Prisma } from '@prisma/client';
import type { IAuditEventRepository, AuditEventFilter, AuditEventCreateInput, AuditSessionSummary } from './interfaces.js';

export class AuditEventRepository implements IAuditEventRepository {
  constructor(private readonly prisma: PrismaClient) {}

  async findAll(filter?: AuditEventFilter): Promise<AuditEvent[]> {
    const where = buildWhere(filter);
    return this.prisma.auditEvent.findMany({
      where,
      orderBy: { timestamp: 'desc' },
      take: filter?.limit ?? 100,
      skip: filter?.offset ?? 0,
    });
  }

  async findById(id: string): Promise<AuditEvent | null> {
    return this.prisma.auditEvent.findUnique({ where: { id } });
  }

  async createMany(events: AuditEventCreateInput[]): Promise<number> {
    const data = events.map((e) => ({
      timestamp: new Date(e.timestamp),
      sessionId: e.sessionId,
      projectName: e.projectName,
      eventKind: e.eventKind,
      source: e.source,
      verified: e.verified,
      serverName: e.serverName ?? null,
      correlationId: e.correlationId ?? null,
      parentEventId: e.parentEventId ?? null,
      userName: e.userName ?? null,
      payload: e.payload as Prisma.InputJsonValue,
    }));
    const result = await this.prisma.auditEvent.createMany({ data });
    return result.count;
  }

  async count(filter?: AuditEventFilter): Promise<number> {
    const where = buildWhere(filter);
    return this.prisma.auditEvent.count({ where });
  }

  async listSessions(filter?: { projectName?: string; userName?: string; from?: Date; to?: Date; limit?: number; offset?: number }): Promise<AuditSessionSummary[]> {
    const where: Prisma.AuditEventWhereInput = {};
    if (filter?.projectName !== undefined) where.projectName = filter.projectName;
    if (filter?.userName !== undefined) where.userName = filter.userName;
    if (filter?.from !== undefined || filter?.to !== undefined) {
      const timestamp: Prisma.DateTimeFilter = {};
      if (filter?.from !== undefined) timestamp.gte = filter.from;
      if (filter?.to !== undefined) timestamp.lte = filter.to;
      where.timestamp = timestamp;
    }

    const groups = await this.prisma.auditEvent.groupBy({
      by: ['sessionId', 'projectName'],
      where,
      _min: { timestamp: true },
      _max: { timestamp: true },
      _count: true,
      orderBy: { _max: { timestamp: 'desc' } },
      take: filter?.limit ?? 50,
      skip: filter?.offset ?? 0,
    });

    // Fetch distinct eventKinds + first userName per session
    const sessionIds = groups.map((g) => g.sessionId);
    const [kindRows, userNameRows] = sessionIds.length > 0
      ? await Promise.all([
          this.prisma.auditEvent.findMany({
            where: { sessionId: { in: sessionIds } },
            select: { sessionId: true, eventKind: true },
            distinct: ['sessionId', 'eventKind'],
          }),
          this.prisma.auditEvent.findMany({
            where: { sessionId: { in: sessionIds }, userName: { not: null } },
            select: { sessionId: true, userName: true },
            distinct: ['sessionId'],
          }),
        ])
      : [[], []];

    const kindMap = new Map<string, string[]>();
    for (const row of kindRows) {
      const list = kindMap.get(row.sessionId) ?? [];
      list.push(row.eventKind);
      kindMap.set(row.sessionId, list);
    }

    const userMap = new Map<string, string>();
    for (const row of userNameRows) {
      if (row.userName) userMap.set(row.sessionId, row.userName);
    }

    return groups.map((g) => ({
      sessionId: g.sessionId,
      projectName: g.projectName,
      userName: userMap.get(g.sessionId) ?? null,
      firstSeen: g._min.timestamp!,
      lastSeen: g._max.timestamp!,
      eventCount: g._count,
      eventKinds: kindMap.get(g.sessionId) ?? [],
    }));
  }

  async countSessions(filter?: { projectName?: string; userName?: string; from?: Date; to?: Date }): Promise<number> {
    const where: Prisma.AuditEventWhereInput = {};
    if (filter?.projectName !== undefined) where.projectName = filter.projectName;
    if (filter?.userName !== undefined) where.userName = filter.userName;
    if (filter?.from !== undefined || filter?.to !== undefined) {
      const timestamp: Prisma.DateTimeFilter = {};
      if (filter?.from !== undefined) timestamp.gte = filter.from;
      if (filter?.to !== undefined) timestamp.lte = filter.to;
      where.timestamp = timestamp;
    }

    const groups = await this.prisma.auditEvent.groupBy({
      by: ['sessionId'],
      where,
    });
    return groups.length;
  }
}

function buildWhere(filter?: AuditEventFilter): Prisma.AuditEventWhereInput {
  const where: Prisma.AuditEventWhereInput = {};
  if (!filter) return where;

  if (filter.sessionId !== undefined) where.sessionId = filter.sessionId;
  if (filter.projectName !== undefined) where.projectName = filter.projectName;
  if (filter.eventKind !== undefined) where.eventKind = filter.eventKind;
  if (filter.serverName !== undefined) where.serverName = filter.serverName;
  if (filter.correlationId !== undefined) where.correlationId = filter.correlationId;
  if (filter.userName !== undefined) where.userName = filter.userName;

  if (filter.from !== undefined || filter.to !== undefined) {
    const timestamp: Prisma.DateTimeFilter = {};
    if (filter.from !== undefined) timestamp.gte = filter.from;
    if (filter.to !== undefined) timestamp.lte = filter.to;
    where.timestamp = timestamp;
  }

  return where;
}
