import { db } from '@/lib/db';
import { auditLogs } from '@/lib/db/schema';
import { logger } from '@/lib/logger';

export type AuditAction =
  | 'create'
  | 'update'
  | 'delete'
  | 'archive'
  | 'restore'
  | 'merge'
  | 'login'
  | 'logout'
  | 'permission_denied'
  | 'revert'
  | 'revoke_invite'
  | 'resend_invite'
  | 'request_gdpr_export'
  | 'send_gdpr_export';

export interface AuditLogParams {
  /** Null for system-generated events. */
  userId: string | null;
  /** Null for system-level events not tied to a port. */
  portId: string | null;
  action: AuditAction;
  entityType: string;
  entityId: string;
  fieldChanged?: string;
  oldValue?: Record<string, unknown>;
  newValue?: Record<string, unknown>;
  metadata?: Record<string, unknown>;
  ipAddress: string;
  userAgent: string;
}

const SENSITIVE_FIELDS = new Set(['email', 'phone', 'password', 'credentials_enc', 'token']);

/**
 * Masks sensitive field values to prevent PII or secrets from being stored
 * verbatim in the audit log (SECURITY-GUIDELINES.md §5.2).
 *
 * Strings are replaced with a partial mask — first 2 chars + *** + last 2 chars.
 */
export function maskSensitiveFields(
  data?: Record<string, unknown>,
): Record<string, unknown> | undefined {
  if (!data) return undefined;
  const masked = { ...data };
  for (const key of Object.keys(masked)) {
    if (SENSITIVE_FIELDS.has(key) && typeof masked[key] === 'string') {
      const val = masked[key] as string;
      masked[key] = val.length > 4 ? `${val.slice(0, 2)}***${val.slice(-2)}` : '***';
    }
  }
  return masked;
}

/**
 * Computes a field-level diff between two records.
 * Returns one entry per changed field with the old and new values.
 */
export function diffFields(
  oldRecord: Record<string, unknown>,
  newRecord: Record<string, unknown>,
): Array<{ field: string; oldValue: unknown; newValue: unknown }> {
  const changes: Array<{ field: string; oldValue: unknown; newValue: unknown }> = [];
  for (const key of Object.keys(newRecord)) {
    if (JSON.stringify(oldRecord[key]) !== JSON.stringify(newRecord[key])) {
      changes.push({ field: key, oldValue: oldRecord[key], newValue: newRecord[key] });
    }
  }
  return changes;
}

/**
 * Inserts an audit log entry into the database.
 *
 * This function NEVER throws — errors are caught and logged so that an audit
 * failure never rolls back or disrupts the parent operation.
 */
export async function createAuditLog(params: AuditLogParams): Promise<void> {
  try {
    await db.insert(auditLogs).values({
      portId: params.portId,
      userId: params.userId,
      action: params.action,
      entityType: params.entityType,
      entityId: params.entityId,
      fieldChanged: params.fieldChanged ?? null,
      oldValue: maskSensitiveFields(params.oldValue) ?? null,
      newValue: maskSensitiveFields(params.newValue) ?? null,
      metadata: params.metadata ?? null,
      ipAddress: params.ipAddress,
      userAgent: params.userAgent,
    });
  } catch (err) {
    // Strip old/new values from the log to avoid secondary exposure of the data
    // that just failed to persist.
    logger.error(
      {
        err,
        audit: {
          userId: params.userId,
          portId: params.portId,
          action: params.action,
          entityType: params.entityType,
          entityId: params.entityId,
        },
      },
      'Failed to write audit log',
    );
  }
}
