// packages/functions/src/shared/utils/internal/idempotency.ts

/**
 * @fileoverview Idempotency utilities for functions
 * @description Provides idempotency checking and storage for function calls
 *
 * @version 0.0.1
 * @since 0.0.1
 * @author AMBROISE PARK Consulting
 */

import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
import { logger } from 'firebase-functions/v2';

export interface IdempotencyResult<T = any> {
  result: T;
  processedAt: string;
  processedBy: string;
}

/**
 * Check if an operation has already been processed (idempotency check)
 *
 * @version 0.0.1
 * @since 0.0.1
 * @author AMBROISE PARK Consulting
 */
export async function checkIdempotency<T = any>(
  key: string,
  operation: string
): Promise<T | null> {
  const db = getFirebaseAdminFirestore();
  try {
    const idempotencyRef = db
      .collection('idempotency')
      .doc(`${operation}_${key}`);
    const idempotencyDoc = await idempotencyRef.get();

    if (idempotencyDoc.exists) {
      const data = idempotencyDoc.data() as IdempotencyResult<T>;
      logger.info('Operation already processed (idempotency)', {
        key,
        operation,
        processedAt: data.processedAt,
        processedBy: data.processedBy,
      });
      return data.result;
    }

    return null;
  } catch (error) {
    logger.error('Idempotency check failed', {
      key,
      operation,
      error: error instanceof Error ? error.message : String(error),
    });
    // Fail open - allow the operation if idempotency check fails
    return null;
  }
}

/**
 * Store the result of an operation for idempotency
 *
 * @version 0.0.1
 * @since 0.0.1
 * @author AMBROISE PARK Consulting
 */
export async function storeIdempotencyResult<T = any>(
  key: string,
  operation: string,
  result: T,
  processedBy: string
): Promise<void> {
  try {
    const db = getFirebaseAdminFirestore();
    const idempotencyRef = db
      .collection('idempotency')
      .doc(`${operation}_${key}`);
    await idempotencyRef.set({
      result,
      processedAt: new Date().toISOString(),
      processedBy,
    });

    logger.info('Idempotency result stored', {
      key,
      operation,
      processedBy,
    });
  } catch (error) {
    logger.error('Failed to store idempotency result', {
      key,
      operation,
      processedBy,
      error: error instanceof Error ? error.message : String(error),
    });
    // Don't throw - idempotency storage failure shouldn't break the operation
  }
}

/**
 * Generate a deterministic idempotency key from request data
 *
 * @version 0.0.1
 * @since 0.0.1
 * @author AMBROISE PARK Consulting
 */
export function generateIdempotencyKey(
  operation: string,
  userId: string,
  ...data: any[]
): string {
  // Create a deterministic key from operation, user, and data
  const keyData = [operation, userId, ...data].filter(Boolean);
  const keyString = keyData.join('_');

  // Simple hash function for consistent key generation
  let hash = 0;
  for (let i = 0; i < keyString.length; i++) {
    const char = keyString.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash = hash & hash; // Convert to 32-bit integer
  }

  return Math.abs(hash).toString(36);
}

/**
 * Clean up old idempotency records (call periodically)
 *
 * @version 0.0.1
 * @since 0.0.1
 * @author AMBROISE PARK Consulting
 */
export async function cleanupIdempotencyRecords(
  olderThanDays: number = 30
): Promise<void> {
  try {
    const db = getFirebaseAdminFirestore();
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);

    const idempotencyRef = db.collection('idempotency');
    const oldRecords = await idempotencyRef
      .where('processedAt', '<', cutoffDate.toISOString())
      .limit(500) // Process in batches
      .get();

    if (oldRecords.empty) {
      return;
    }

    const batch = db.batch();
    oldRecords.docs.forEach((doc: any) => {
      batch.delete(doc.ref);
    });

    await batch.commit();

    logger.info('Idempotency cleanup completed', {
      deletedCount: oldRecords.size,
      cutoffDate: cutoffDate.toISOString(),
    });
  } catch (error) {
    logger.error('Idempotency cleanup failed', {
      error: error instanceof Error ? error.message : String(error),
    });
  }
}
