import type { PrismaClient, Prisma } from "@capakraken/db";
import { logger } from "./logger.js";

type AuditAction = "CREATE" | "UPDATE" | "DELETE" | "SHIFT" | "IMPORT";
type AuditSource = "ui" | "api" | "ai" | "import" | "cron";

interface CreateAuditEntryParams {
  db: PrismaClient;
  entityType: string;
  entityId: string;
  entityName?: string;
  action: AuditAction;
  userId?: string;
  before?: Record<string, unknown>;
  after?: Record<string, unknown>;
  source?: AuditSource;
  summary?: string;
  metadata?: Record<string, unknown>;
}

const INTERNAL_FIELDS = new Set(["id", "createdAt", "updatedAt"]);

/**
 * Compare two snapshots and return only the changed fields.
 * Skips internal fields (id, createdAt, updatedAt).
 * Uses JSON.stringify for nested object comparison.
 */
export function computeDiff(
  before: Record<string, unknown>,
  after: Record<string, unknown>,
): Record<string, { old: unknown; new: unknown }> {
  const diff: Record<string, { old: unknown; new: unknown }> = {};

  const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);

  for (const key of allKeys) {
    if (INTERNAL_FIELDS.has(key)) continue;

    const oldVal = before[key];
    const newVal = after[key];

    // Compare by JSON serialization to handle nested objects/arrays
    const oldStr = JSON.stringify(oldVal) ?? "undefined";
    const newStr = JSON.stringify(newVal) ?? "undefined";

    if (oldStr !== newStr) {
      diff[key] = { old: oldVal, new: newVal };
    }
  }

  return diff;
}

/**
 * Auto-generate a human-readable summary from the action and diff.
 */
export function generateSummary(
  action: string,
  entityType: string,
  diff?: Record<string, { old: unknown; new: unknown }>,
): string {
  switch (action) {
    case "CREATE":
      return `Created ${entityType}`;
    case "DELETE":
      return `Deleted ${entityType}`;
    case "SHIFT":
      return `Shifted ${entityType}`;
    case "IMPORT":
      return `Imported ${entityType}`;
    case "UPDATE": {
      if (!diff || Object.keys(diff).length === 0) {
        return `Updated ${entityType}`;
      }
      const fields = Object.keys(diff);
      if (fields.length <= 3) {
        return `Updated ${fields.join(", ")}`;
      }
      return `Updated ${fields.slice(0, 3).join(", ")} and ${fields.length - 3} more`;
    }
    default:
      return `${action} ${entityType}`;
  }
}

/**
 * Create an audit log entry. Fire-and-forget — errors are logged but never thrown.
 *
 * If both `before` and `after` are provided, a diff is computed automatically.
 * If no `summary` is given, one is generated from the action and diff.
 */
export async function createAuditEntry(params: CreateAuditEntryParams): Promise<void> {
  try {
    const { db, entityType, entityId, entityName, action, userId, before, after, source, metadata } = params;
    const auditLog = (db as Partial<PrismaClient>).auditLog;

    if (!auditLog || typeof auditLog.create !== "function") {
      return;
    }

    // Compute diff if both snapshots are available
    const diff = before && after ? computeDiff(before, after) : undefined;

    // Skip UPDATE entries where nothing actually changed
    if (action === "UPDATE" && diff && Object.keys(diff).length === 0) {
      return;
    }

    // Auto-generate summary if not provided
    const summary = params.summary ?? generateSummary(action, entityType, diff);

    // Build the changes JSONB payload
    const changes: Record<string, unknown> = {};
    if (before) changes.before = before;
    if (after) changes.after = after;
    if (diff) changes.diff = diff;
    if (metadata) changes.metadata = metadata;

    await auditLog.create({
      data: {
        entityType,
        entityId,
        action,
        userId: userId ?? null,
        changes: changes as unknown as Prisma.InputJsonValue,
        source: source ?? null,
        entityName: entityName ?? null,
        summary,
      },
    });
  } catch (error) {
    // Fire-and-forget: log but never propagate
    logger.error({ err: error, entityType: params.entityType, entityId: params.entityId }, "Failed to create audit entry");
  }
}
