import type { FastifyRequest } from "fastify";
import { prisma } from "../lib/prisma";
import type { AuditAction, AuditCategory, AuditLogFilters } from "@library/shared-types";

interface AuditLogData {
  action: AuditAction;
  category: AuditCategory;
  userId?: string;
  targetUserId?: string;
  resourceType?: string;
  resourceId?: string;
  details?: string;
  success?: boolean;
}

export const AuditService = {
  async log(data: AuditLogData, request?: FastifyRequest) {
    const userAgent = request?.headers["user-agent"] ?? undefined;

    return prisma.auditLog.create({
      data: {
        action: data.action,
        category: data.category,
        userId: data.userId,
        targetUserId: data.targetUserId,
        resourceType: data.resourceType,
        resourceId: data.resourceId,
        details: data.details,
        userAgent,
        success: data.success ?? true,
      },
    });
  },

  async logFromRequest(
    request: FastifyRequest,
    data: Omit<AuditLogData, "userId">
  ) {
    const userId = ((request as any).user as { id?: string } | undefined)?.id;

    return this.log(
      {
        ...data,
        userId,
      },
      request
    );
  },

  async getLogs(filters: AuditLogFilters = {}) {
    const { action, category, userId, success, startDate, endDate, page = 1, limit = 50 } = filters;

    const where: Record<string, unknown> = {};

    if (action) {
      where.action = action;
    }
    if (category) {
      where.category = category;
    }
    if (userId) {
      where.userId = userId;
    }
    if (success !== undefined) {
      where.success = success;
    }
    if (startDate || endDate) {
      where.createdAt = {};
      if (startDate) {
        (where.createdAt as Record<string, Date>).gte = startDate;
      }
      if (endDate) {
        (where.createdAt as Record<string, Date>).lte = endDate;
      }
    }

    const [rawLogs, total] = await Promise.all([
      prisma.auditLog.findMany({
        where,
        orderBy: { createdAt: "desc" },
        skip: (page - 1) * limit,
        take: limit,
      }),
      prisma.auditLog.count({ where }),
    ]);

    // Collect all unique user IDs to fetch
    const userIds = new Set<string>();
    for (const log of rawLogs) {
      if (log.userId) {
        userIds.add(log.userId);
      }
      if (log.targetUserId) {
        userIds.add(log.targetUserId);
      }
    }

    // Fetch all users in one query
    const users = userIds.size > 0
      ? await prisma.user.findMany({
          where: { id: { in: Array.from(userIds) } },
          select: { id: true, username: true, avatar: true },
        })
      : [];

    // Create a lookup map
    const userMap = new Map(users.map(u => [u.id, { id: u.id, username: u.username, avatar: u.avatar ?? undefined }]));

    // Map logs with user info
    const logs = rawLogs.map(log => ({
      id: log.id,
      action: log.action,
      category: log.category,
      userId: log.userId ?? undefined,
      user: log.userId ? userMap.get(log.userId) : undefined,
      targetUserId: log.targetUserId ?? undefined,
      targetUser: log.targetUserId ? userMap.get(log.targetUserId) : undefined,
      resourceType: log.resourceType ?? undefined,
      resourceId: log.resourceId ?? undefined,
      details: log.details ?? undefined,
      userAgent: log.userAgent ?? undefined,
      success: log.success,
      createdAt: log.createdAt,
    }));

    return {
      logs,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  },

  async getLogsByUser(userId: string, page = 1, limit = 50) {
    return this.getLogs({ userId, page, limit });
  },

  async getSecurityLogs(page = 1, limit = 50) {
    return this.getLogs({ category: "SECURITY" as AuditCategory, page, limit });
  },
};
