// packages/functions/src/firebase/crud/update.ts

/**
 * @fileoverview Generic function to update an entity.
 * @description Provides a reusable implementation for updating documents in Firestore with validation.
 *
 * @version 0.0.1
 * @since 0.0.1
 * @author AMBROISE PARK Consulting
 */

import * as v from 'valibot';

import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';

import {
  prepareForFirestore,
  transformFirestoreData,
} from '../../shared/index.js';
import { updateMetadata } from '../../shared/index.js';
import {
  assertAdmin,
  validateDocument,
  DoNotDevError,
} from '../../shared/utils.js';
import { createBaseFunction } from '../baseFunction.js';
import { CRUD_CONFIG } from '../config/constants.js';

import type { CallableRequest } from 'firebase-functions/v2/https';

/**
 * Generic business logic for updating entities
 * Base function handles: validation, auth, rate limiting, monitoring
 */
function updateEntityLogicFactory(
  collection: string,
  documentSchema: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
) {
  return async function updateEntityLogic(
    data: { id: string; payload: Record<string, any>; idempotencyKey?: string },
    context: { uid: string; request: CallableRequest<any> }
  ) {
    const { id, payload, idempotencyKey } = data;

    // Ensure the user is an admin
    const uid = await assertAdmin(context.uid);

    // Idempotency check if key provided
    if (idempotencyKey) {
      const db = getFirebaseAdminFirestore();
      const idempotencyRef = db
        .collection('idempotency')
        .doc(`update_${idempotencyKey}`);
      const idempotencyDoc = await idempotencyRef.get();
      if (idempotencyDoc.exists) {
        return idempotencyDoc.data()?.result;
      }
    }

    // Get the document reference
    const db = getFirebaseAdminFirestore();
    const docRef = db.collection(collection).doc(id);

    // Check if the document exists
    const doc = await docRef.get();
    if (!doc.exists) {
      throw new DoNotDevError('Entity not found', 'not-found');
    }

    // Get the current data and merge with the update payload
    const currentData = transformFirestoreData(doc.data());
    const mergedData = { ...currentData, ...payload };

    // Validate the merged document against the schema
    validateDocument(mergedData, documentSchema);

    // Prepare the update data for Firestore and add metadata
    const updateData = {
      ...prepareForFirestore(payload),
      ...updateMetadata(uid),
    };

    // Update the document
    await docRef.update(updateData);

    // Retrieve the updated document
    const updatedDoc = await docRef.get();

    // Transform the document data back to the application format
    const result = transformFirestoreData({
      id: updatedDoc.id,
      ...updatedDoc.data(),
    });

    // Store result for idempotency if key provided
    if (idempotencyKey) {
      const idempotencyRef = db
        .collection('idempotency')
        .doc(`update_${idempotencyKey}`);
      await idempotencyRef.set({
        result,
        processedAt: new Date().toISOString(),
        processedBy: uid,
      });
    }

    return result;
  };
}

/**
 * Generic function to update entities in any Firestore collection
 * @param collection - The Firestore collection name
 * @param documentSchema - The Valibot schema for document validation
 * @param customSchema - Optional custom request schema
 * @returns Firebase callable function
 */
export const updateEntity = (
  collection: string,
  documentSchema: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>,
  customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
) => {
  const requestSchema =
    customSchema ||
    v.object({
      id: v.pipe(v.string(), v.minLength(1)),
      payload: v.record(v.string(), v.any()),
      idempotencyKey: v.optional(v.string()),
    });

  return createBaseFunction(
    CRUD_CONFIG,
    requestSchema,
    'update_entity',
    updateEntityLogicFactory(collection, documentSchema)
  );
};
