/**
 * Safe logging utility to prevent circular structure errors and limit log size
 */

const MAX_LOG_SIZE = 10000; // Maximum characters for a single log field
const MAX_ARRAY_ITEMS = 50; // Maximum array items to log

/**
 * Safely stringify an object, handling circular references and limiting size
 * @param obj The object to stringify
 * @param maxDepth Maximum depth to traverse
 * @returns Safe string representation
 */
export function safeStringify(obj: any, maxDepth = 3): string {
  const seen = new WeakSet();

  const stringify = (value: any, depth: number): any => {
    // Handle null and undefined
    if (value === null) return null;
    if (value === undefined) return undefined;

    // Handle primitives
    if (typeof value !== 'object') {
      return value;
    }

    // Check for circular reference
    if (seen.has(value)) {
      return '[Circular]';
    }

    // Check depth limit
    if (depth >= maxDepth) {
      return '[Max Depth Reached]';
    }

    seen.add(value);

    try {
      // Handle Date
      if (value instanceof Date) {
        return value.toISOString();
      }

      // Handle Array
      if (Array.isArray(value)) {
        const items = value
          .slice(0, MAX_ARRAY_ITEMS)
          .map((item) => stringify(item, depth + 1));
        if (value.length > MAX_ARRAY_ITEMS) {
          items.push(`[... ${value.length - MAX_ARRAY_ITEMS} more items]`);
        }
        return items;
      }

      // Handle plain objects
      const result: any = {};
      const keys = Object.keys(value);

      for (const key of keys) {
        try {
          result[key] = stringify(value[key], depth + 1);
        } catch (error) {
          result[key] = '[Error stringifying]';
        }
      }

      return result;
    } finally {
      seen.delete(value);
    }
  };

  try {
    const safeObj = stringify(obj, 0);
    const str = JSON.stringify(safeObj);

    // Limit total size
    if (str.length > MAX_LOG_SIZE) {
      return (
        str.substring(0, MAX_LOG_SIZE) +
        `... [truncated ${str.length - MAX_LOG_SIZE} chars]`
      );
    }

    return str;
  } catch (error) {
    return `[Error: ${error.message}]`;
  }
}

/**
 * Create a safe log object that can be passed to logger
 * @param data The data to log
 * @returns Safe log object
 */
export function createSafeLogData(
  data: Record<string, any>,
): Record<string, any> {
  const safeData: Record<string, any> = {
    __ROLE_PAGE_RESOURCE__: true, // Unified field for production log collection
  };

  for (const [key, value] of Object.entries(data)) {
    try {
      // For simple types, use directly
      if (
        value === null ||
        value === undefined ||
        typeof value === 'string' ||
        typeof value === 'number' ||
        typeof value === 'boolean'
      ) {
        safeData[key] = value;
      } else {
        // For complex types, use safe stringify
        const stringified = safeStringify(value);
        try {
          // Try to parse back to object for better log formatting
          safeData[key] = JSON.parse(stringified);
        } catch {
          // If parse fails, use the string
          safeData[key] = stringified;
        }
      }
    } catch (error) {
      safeData[key] = `[Error processing ${key}]`;
    }
  }

  return safeData;
}

/**
 * Extract only essential fields from database records for logging
 * @param record Database record
 * @param essentialFields Fields to include (defaults to common fields)
 * @returns Simplified record
 */
export function extractEssentialFields(
  record: any,
  essentialFields?: string[],
): any {
  if (!record) return record;

  const defaultFields = [
    'id',
    'name',
    'code',
    'type',
    'status',
    'createdAt',
    'updatedAt',
  ];
  const fields = essentialFields || defaultFields;

  if (Array.isArray(record)) {
    return record.map((item) => extractEssentialFields(item, essentialFields));
  }

  if (typeof record === 'object') {
    const result: any = {};
    for (const field of fields) {
      if (field in record) {
        result[field] = record[field];
      }
    }
    // Always include id if exists
    if ('id' in record && !result.id) {
      result.id = record.id;
    }
    return result;
  }

  return record;
}
