import dotenv from 'dotenv';
import { fileModel } from './schemas/management/file.schema.js';
import _ from 'lodash';
import {
  deleteAuditLog,
  distributeDelete,
  expandObjectIds,
  modelHasRef,
  getFieldsByRef,
  getQueryToCacheKey,
} from '../utils.js';
import log4js from 'log4js';
import {
  editAuditLog,
  distributeUpdate,
  newAuditLog,
  distributeNew,
  distributeChildUpdate,
  distributeChildDelete,
  distributeChildNew,
  distributeStats,
} from '../utils.js';
import { getAllModels } from '../services/misc/model.js';
import { redisServer } from './redis.js';
import { auditLogModel } from './schemas/management/auditlog.schema.js';

dotenv.config();

const logger = log4js.getLogger('Database');
logger.level = process.env.LOG_LEVEL;

const cacheLogger = log4js.getLogger('DatabaseCache');
cacheLogger.level = process.env.LOG_LEVEL;

const CACHE_TTL_SECONDS = parseInt(process.env.REDIS_CACHE_TTL || '30', 10);

export const retrieveObjectCache = async ({ model, id, populate = [] }) => {
  if (!model || !id) return undefined;

  const cacheKey = getQueryToCacheKey({ model: model.modelName, id, populate });

  cacheLogger.trace('Retrieving object from cache:', { model: model.modelName, id, populate });

  try {
    const cachedObject = await redisServer.getKey(cacheKey);
    if (cachedObject == null) {
      cacheLogger.trace('Cache miss:', { model: model.modelName, id });
      return undefined;
    }

    cacheLogger.trace('Cache hit:', {
      model: model.modelName,
      id: id.toString(),
    });

    return cachedObject;
  } catch (err) {
    cacheLogger.error('Error retrieving object from Redis cache:', err);
    return undefined;
  }
};

export const updateObjectCache = async ({ model, id, object, populate = [] }) => {
  if (!model || !id || !object) return object;

  const cacheKeyFilter = `${model.modelName}:${id?.toString()}*`;
  const cacheKey = getQueryToCacheKey({ model: model.modelName, id, populate });

  cacheLogger.trace('Updating object cache:', cacheKeyFilter);

  try {
    // Get all keys matching the filter pattern
    const matchingKeys = await redisServer.getKeysByPattern(cacheKeyFilter);
    // Merge the object with each cached object and update
    const mergedObjects = [];
    for (const key of matchingKeys) {
      logger.trace('Updating object cache:', key);
      const cachedObject = (await redisServer.getKey(key)) || {};
      const mergedObject = _.merge(cachedObject, object);
      await redisServer.setKey(key, mergedObject, CACHE_TTL_SECONDS);
      mergedObjects.push(mergedObject);
    }

    const cacheObject = (await redisServer.getKey(cacheKey)) || {};
    const mergedObject = _.merge(cacheObject, object);
    await redisServer.setKey(cacheKey, mergedObject, CACHE_TTL_SECONDS);

    cacheLogger.trace('Updated object cache:', {
      filter: cacheKeyFilter,
      keysUpdated: matchingKeys.length,
    });

    // Return the merged object
    return mergedObject;
  } catch (err) {
    cacheLogger.error('Error updating object in Redis cache:', err);
    // Fallback to returning the provided object if cache fails
    return object;
  }
};

export const deleteObjectCache = async ({ model, id }) => {
  if (!model || !id) return;

  const cacheKeyFilter = `${model.modelName}:${id?.toString()}*`;

  cacheLogger.trace('Deleting object cache:', cacheKeyFilter);

  try {
    // Get all keys matching the filter pattern and delete them
    const matchingKeys = await redisServer.getKeysByPattern(cacheKeyFilter);

    for (const cacheKey of matchingKeys) {
      await redisServer.deleteKey(cacheKey);
    }

    cacheLogger.trace('Deleted object cache:', {
      filter: cacheKeyFilter,
      keysDeleted: matchingKeys.length,
    });
  } catch (err) {
    cacheLogger.error('Error deleting object from Redis cache:', err);
  }
};

// Utility to run one or many rollup aggregations in a single query via $facet.
export const aggregateRollups = async ({ model, baseFilter = {}, rollupConfigs = [] }) => {
  if (!rollupConfigs.length) {
    return {};
  }

  const facetStage = rollupConfigs.reduce((facets, definition, index) => {
    const key = definition.name || `rollup${index}`;
    const matchStage = { $match: { ...baseFilter, ...(definition.filter || {}) } };
    const groupStage = { $group: { _id: null } };

    (definition.rollups || []).forEach((rollup) => {
      switch (rollup.operation) {
        case 'sum':
          groupStage.$group[rollup.name] = { $sum: `$${rollup.property}` };
          break;
        case 'count':
          groupStage.$group[rollup.name] = { $sum: 1 };
          break;
        case 'avg':
          groupStage.$group[rollup.name] = { $avg: `$${rollup.property}` };
          break;
        default:
          throw new Error(`Unsupported rollup operation: ${rollup.operation}`);
      }
    });

    facets[key] = [matchStage, groupStage];
    return facets;
  }, {});

  const [results] = await model.aggregate([{ $facet: facetStage }]);

  return rollupConfigs.reduce((acc, definition, index) => {
    const key = definition.name || `rollup${index}`;
    const rawResult = results?.[key]?.[0] || {};

    // Transform the result to nest rollup values under operation type
    const transformedResult = {};
    (definition.rollups || []).forEach((rollup) => {
      const value = rawResult[rollup.name] || 0;
      // If there's only one rollup and its name matches the key, flatten the structure
      if (definition.rollups.length === 1 && rollup.name === key) {
        transformedResult[rollup.operation] = value;
      } else {
        transformedResult[rollup.name] = { [rollup.operation]: value };
      }
    });

    acc[key] = transformedResult;
    return acc;
  }, {});
};

// Reusable function to aggregate rollups over history using state reconstruction
export const aggregateRollupsHistory = async ({
  model,
  baseFilter = {},
  rollupConfigs = [],
  startDate,
  endDate,
}) => {
  if (!rollupConfigs.length) {
    return [];
  }

  // Set default dates if not provided
  const end = endDate ? new Date(endDate) : new Date();
  const start = startDate ? new Date(startDate) : new Date(end.getTime() - 24 * 60 * 60 * 1000);

  // Get model name for filtering audit logs
  const parentType = model.modelName ? model.modelName : 'unknown';

  // 1. Fetch all audit logs for this model type from start date to now
  // Filter by parentType instead of fetching object IDs first
  const auditLogs = await auditLogModel
    .find({
      parentType,
      createdAt: { $gte: start },
    })
    .sort({ createdAt: -1 }) // Newest first
    .lean();

  // 2. Extract unique parent IDs from audit logs
  const parentIds = [...new Set(auditLogs.map((log) => log.parent.toString()))];

  if (parentIds.length === 0) {
    return [];
  }

  // 3. Fetch current state of relevant objects that match baseFilter
  // Note: This only includes objects that CURRENTLY match the baseFilter.
  // Objects that matched in the past but don't match now are excluded.
  const currentObjects = await model
    .find({
      _id: { $in: parentIds },
      ...baseFilter,
    })
    .lean();
  const objectMap = new Map();
  currentObjects.forEach((obj) => {
    // Ensure _id is a string for map keys
    objectMap.set(obj._id.toString(), expandObjectIds(obj));
  });

  if (objectMap.size === 0) {
    return [];
  }

  // Helper to check if object matches filter
  const matchesFilter = (obj, filter) => {
    if (!filter || Object.keys(filter).length === 0) return true;

    for (const [key, expectedValue] of Object.entries(filter)) {
      const actualValue = _.get(obj, key);

      // Handle simple equality
      if (actualValue != expectedValue) {
        return false;
      }
    }
    return true;
  };

  // 3. Generate time buckets (1 minute intervals)
  const buckets = [];
  let currentTime = new Date(end);
  // Round down to nearest minute
  currentTime.setSeconds(0, 0);

  while (currentTime >= start) {
    buckets.push(new Date(currentTime));
    currentTime = new Date(currentTime.getTime() - 60000); // -1 minute
  }

  // 4. Rewind state and snapshot
  const results = [];
  let logIndex = 0;

  // Create a working copy of objects to mutate during rewind
  // (deep clone to avoid issues if we need original later, though expandObjectIds creates new objs)
  const workingObjects = new Map();
  objectMap.forEach((val, key) => workingObjects.set(key, _.cloneDeep(val)));

  // Iterate backwards through time
  for (const bucketDate of buckets) {
    // Apply all logs that happened AFTER this bucket time (between last bucket and this one)
    // Since we iterate backwards, these are logs with createdAt > bucketDate
    while (logIndex < auditLogs.length) {
      const log = auditLogs[logIndex];
      const logDate = new Date(log.createdAt);

      if (logDate <= bucketDate) {
        // This log happened at or before the current bucket time,
        // so its effects are already present (or rather, will be handled in a future/earlier bucket).
        // Stop processing logs for this step.
        break;
      }

      // Revert this change
      const objectId = log.parent.toString();
      const object = workingObjects.get(objectId);

      if (object) {
        if (log.operation === 'new') {
          // Object didn't exist before this creation event
          workingObjects.delete(objectId);
        } else if (log.changes && log.changes.old) {
          // Apply old values to revert state
          _.merge(object, log.changes.old);
        }
      }

      logIndex++;
    }

    // Snapshot: Calculate rollups for current state of all objects
    const bucketResult = {
      date: bucketDate.toISOString(),
    };

    const activeObjects = Array.from(workingObjects.values());

    rollupConfigs.forEach((config) => {
      const configName = config.name;

      // Filter objects for this config
      // Note: We also check baseFilter here in case the object state reverted to something
      // that no longer matches baseFilter (e.g. active: false)
      const matchingObjects = activeObjects.filter(
        (obj) => matchesFilter(obj, baseFilter) && matchesFilter(obj, config.filter)
      );

      // Calculate rollups
      (config.rollups || []).forEach((rollup) => {
        const rollupName = rollup.name;

        let value = 0;
        if (rollup.operation === 'count') {
          value = matchingObjects.length;
        } else if (rollup.operation === 'sum') {
          value = _.sumBy(matchingObjects, (obj) => _.get(obj, rollup.property) || 0);
        } else if (rollup.operation === 'avg') {
          const sum = _.sumBy(matchingObjects, (obj) => _.get(obj, rollup.property) || 0);
          value = matchingObjects.length ? sum / matchingObjects.length : 0;
        }

        // Nest the value under the operation type
        bucketResult[rollupName] = { [rollup.operation]: value };
      });
    });

    results.push(bucketResult);
  }

  // Reverse results to be chronological
  return results.reverse();
};

// Reusable function to list objects with aggregation, filtering, search, sorting, and pagination
export const listObjects = async ({
  model,
  populate = [],
  page = 1,
  limit = 25,
  filter = {},
  sort = '',
  order = 'ascend',
  project, // optional: override default projection
}) => {
  try {
    logger.trace('Listing object:', {
      model,
      populate,
      page,
      limit,
      filter,
      sort,
      order,
      project,
    });
    // Calculate the skip value based on the page number and limit
    const skip = (page - 1) * limit;
    // Fix: descend should be -1, ascend should be 1
    const sortOrder = order === 'descend' ? -1 : 1;

    if (!sort || sort === '') {
      sort = 'createdAt';
    }

    if (filter) {
      console.log('filter', filter);
    }

    // Translate any key ending with ._id to remove the ._id suffix for Mongoose
    Object.keys(filter).forEach((key) => {
      if (key.endsWith('._id')) {
        const baseKey = key.slice(0, -4); // Remove '._id' suffix
        filter[baseKey] = filter[key];
        delete filter[key];
      }
    });

    // Use find with population and filter
    let query = model
      .find(filter)
      .sort({ [sort]: sortOrder })
      .skip(skip)
      .limit(Number(limit));

    // Handle populate (array or single value)
    if (populate) {
      if (Array.isArray(populate)) {
        for (const pop of populate) {
          query = query.populate(pop);
        }
      } else if (typeof populate === 'string' || typeof populate === 'object') {
        query = query.populate(populate);
      }
    }

    // Handle select (projection)
    if (project) {
      query = query.select(project);
    }

    query = query.lean();

    const queryResult = await query;
    return expandObjectIds(queryResult);
  } catch (error) {
    logger.error('Object list error:', error);
    return { error: error, code: 500 };
  }
};

// New function to list unique property values
export const listPropertyValues = async ({ model, property, filter = {}, search = '' }) => {
  let aggregateCommand = [];

  if (search) {
    aggregateCommand.push({
      $match: {
        $text: { $search: search },
      },
    });
  }

  if (filter && Object.keys(filter).length > 0) {
    aggregateCommand.push({ $match: filter });
  }

  aggregateCommand.push({ $group: { _id: `$${property}` } });
  aggregateCommand.push({ $project: { _id: 0, [property]: '$_id' } });

  return await model.aggregate(aggregateCommand);
};

// Helper to build nested structure for listObjectsByProperty
function nestGroups(groups, props, filter, idx = 0) {
  if (idx >= props.length) return groups;
  const prop = props[idx];
  const filterPresent = Object.prototype.hasOwnProperty.call(filter, prop);

  // Helper to extract a display key and possible filter values from a value
  function getKeyAndFilterVals(value) {
    if (value && typeof value === 'object') {
      if (value.name) return { key: value.name, filterVals: [value._id?.toString?.(), value.name] };
      if (value._id) return { key: value._id.toString(), filterVals: [value._id.toString()] };
    }
    return { key: value, filterVals: [value] };
  }

  // Build a map of key -> groups for this property
  const keyToGroups = {};
  for (const group of groups) {
    const val = group._id[prop];
    const { key } = getKeyAndFilterVals(val);
    if (!keyToGroups[key]) {
      keyToGroups[key] = {
        groups: [],
        value: val,
      };
    }
    keyToGroups[key].groups.push(group);
  }

  let result = [];

  if (filterPresent) {
    const filterValue = filter[prop]?.toString?.() ?? filter[prop];
    for (const [key, data] of Object.entries(keyToGroups)) {
      const { groups: groupList, value } = data;
      // Check if any group in this key matches the filter (by _id or name)
      const matches = groupList.filter((group) => {
        const { filterVals } = getKeyAndFilterVals(group._id[prop]);
        console.log('filterVals', filterVals);
        console.log('filterValue', filterValue);
        return filterVals.some((val) => val?.toString() === filterValue);
      });

      let children = [];
      if (matches.length > 0) {
        if (idx === props.length - 1) {
          // Last property in filter, return items
          for (const group of matches) {
            children = children.concat(group.objects.map(expandObjectIds));
          }
        } else {
          children = nestGroups(matches, props, filter, idx + 1);
        }
      }

      result.push({
        property: prop,
        value: expandObjectIds(value),
        children: children,
      });
    }
  } else {
    // No filter for this property, just show all keys at this level with empty objects
    for (const [key, data] of Object.entries(keyToGroups)) {
      result.push({
        property: prop,
        value: expandObjectIds(data.value),
        children: [],
      });
    }
  }
  return result;
}

// Group objects by multiple properties and return nested groupings
export const listObjectsByProperties = async ({
  model,
  properties = [],
  filter = {},
  masterFilter = {},
  populate,
}) => {
  try {
    console.log('Props', properties);
    const propertiesPresent = !(
      !Array.isArray(properties) ||
      properties.length === 0 ||
      properties[0] == ''
    );

    // Build aggregation pipeline
    const pipeline = [];

    // Handle populate (array or single value)
    if (populate) {
      const populates = Array.isArray(populate) ? populate : [populate];
      for (const pop of populates) {
        // Support both string and object syntax for populate
        if (typeof pop === 'string') {
          pipeline.push({
            $lookup: {
              from: pop.toLowerCase() + 's', // crude pluralization, adjust if needed
              localField: pop,
              foreignField: '_id',
              as: pop,
            },
          });
          // Unwind if it's a single reference
          pipeline.push({
            $unwind: {
              path: `$${pop}`,
              preserveNullAndEmptyArrays: true,
            },
          });
        } else if (typeof pop === 'object' && pop.path) {
          pipeline.push({
            $lookup: {
              from: pop.from ? pop.from : pop.path.toLowerCase(),
              localField: pop.path,
              foreignField: '_id',
              as: pop.path,
            },
          });
          if (pop?.multiple == false || pop?.multiple == undefined) {
            // default to unwind unless justOne is explicitly false
            pipeline.push({
              $unwind: {
                path: `$${pop.path}`,
                preserveNullAndEmptyArrays: true,
              },
            });
          }
        }
      }
    }

    if (masterFilter != {}) {
      pipeline.push({ $match: { ...masterFilter } });
    }

    if (propertiesPresent) {
      // Build the $group _id object for all properties
      const groupId = {};
      for (const prop of properties) {
        groupId[prop] = `$${prop}`;
      }
      pipeline.push({
        $group: {
          _id: groupId,
          objects: { $push: '$$ROOT' },
        },
      });

      // Run aggregation
      const results = await model.aggregate(pipeline);
      console.log('results', results);
      return nestGroups(results, properties, filter);
    } else {
      // If no properties specified, just return all objects without grouping
      // Ensure pipeline is not empty by adding a $match stage if needed
      if (pipeline.length === 0) {
        pipeline.push({ $match: {} });
      }
      const results = await model.aggregate(pipeline);
      return results;
    }
  } catch (error) {
    logger.error('listObjectsByProperty error:', error);
    return { error: error.message, code: 500 };
  }
};

// Reusable function to get a single object by ID
export const getObject = async ({ model, id, populate }) => {
  try {
    logger.trace('Getting object:', {
      model,
      id,
      populate,
    });

    // Try cache
    const cachedObject = await retrieveObjectCache({ model, id, populate });

    let query = model.findById(id).lean();

    // Auto-populate file references if the model has them
    if (modelHasRef(model, 'file')) {
      const fileFields = getFieldsByRef(model, 'file');

      // Populate all file reference fields
      for (const field of fileFields) {
        query = query.populate(field);
      }
    }

    // Handle populate (array or single value)
    if (populate) {
      if (Array.isArray(populate)) {
        for (const pop of populate) {
          query = query.populate(pop);
        }
      } else if (typeof populate === 'string' || typeof populate === 'object') {
        query = query.populate(populate);
      }
    }
    const result = await query;

    if (!result) {
      return { error: 'Object not found.', code: 404 };
    }

    const expanded = _.merge(cachedObject || {}, expandObjectIds(result));

    // Update cache with the expanded object
    await updateObjectCache({
      model,
      id: expanded._id,
      object: expanded,
      populate,
    });

    return expanded;
  } catch (error) {
    return { error: error, code: 500 };
  }
};

export const getModelStats = async ({ model }) => {
  if (!model.stats) {
    logger.warn(`Model ${model.modelName} does not have a stats method.`);
    return { error: 'Model does not have a stats method.', code: 500 };
  }
  return await model.stats();
};

export const getModelHistory = async ({ model, from, to }) => {
  if (!model.history && !from && !to) {
    logger.warn(`Model ${model.modelName} does not have a history method.`);
    return { error: 'Model does not have a history method.', code: 500 };
  }
  return await model.history(from, to);
};

export const listObjectDependencies = async ({ model, id }) => {
  try {
    const dependencies = [];
    const parentModelName = model?.modelName;
    if (!parentModelName || !id) {
      return [];
    }

    const allModelEntries = getAllModels();

    for (const entry of allModelEntries) {
      const targetModel = entry?.model;
      if (!targetModel || !targetModel.schema) continue;

      const referencingPaths = [];

      targetModel.schema.eachPath((pathName, schemaType) => {
        const directRef = schemaType?.options?.ref;
        const arrayRef = schemaType?.caster?.options?.ref;
        const refName = directRef || arrayRef;
        if (refName === parentModelName) {
          referencingPaths.push(pathName);
        }
      });

      if (referencingPaths.length === 0) continue;

      for (const pathName of referencingPaths) {
        const filter = { [pathName]: id };
        const results = await targetModel.find(filter).lean();
        for (const doc of results) {
          const object = expandObjectIds(doc);
          dependencies.push({
            objectType: targetModel.modelName,
            _id: object._id,
            name: object?.name,
          });
        }
      }
    }

    return dependencies;
  } catch (error) {
    logger.error('listObjectDependencies error:', error);
    return { error: error.message, code: 500 };
  }
};

// Reusable function to edit an object by ID, with audit logging and distribution
export const editObject = async ({ model, id, updateData, user, populate }) => {
  try {
    // Determine parentType from model name
    const parentType = model.modelName ? model.modelName : 'unknown';
    // Fetch the and update object
    var query = model.findByIdAndUpdate(id, updateData).lean();

    if (populate) {
      if (Array.isArray(populate)) {
        for (const pop of populate) {
          query = query.populate(pop);
        }
      } else if (typeof populate === 'string' || typeof populate === 'object') {
        query = query.populate(populate);
      }
    }

    const previousObject = await query;

    if (!previousObject) {
      return { error: `${parentType} not found.`, code: 404 };
    }

    const previousExpandedObject = expandObjectIds(previousObject);

    // Check if any model parameters have ref: 'file' and flush files if so
    if (modelHasRef(model, 'file')) {
      logger.debug(`Model ${model.modelName} has file references, checking for files to flush`);
      const fileFields = getFieldsByRef(model, 'file');

      for (const fieldName of fileFields) {
        const fieldValue = previousExpandedObject[fieldName];

        if (fieldValue) {
          if (Array.isArray(fieldValue)) {
            // Handle file arrays
            for (const fileRef of fieldValue) {
              if (fileRef && fileRef._id) {
                logger.debug(`Flushing file from array field ${fieldName}: ${fileRef._id}`);
                await flushFile({ id: fileRef._id, user });
              }
            }
          } else if (fieldValue._id) {
            // Handle single file reference
            logger.debug(`Flushing file from field ${fieldName}: ${fieldValue._id}`);
            await flushFile({ id: fieldValue._id, user });
          }
        }
      }
    }

    // Audit log before update
    await editAuditLog(
      previousExpandedObject,
      { ...previousExpandedObject, ...updateData },
      id,
      parentType,
      user
    );
    // Distribute update
    await distributeUpdate(updateData, id, parentType);
    // Call childUpdate event for any child objects
    await distributeChildUpdate(
      previousExpandedObject,
      { ...previousExpandedObject, ...updateData },
      id,
      model
    );
    const updatedObject = { ...previousExpandedObject, ...updateData };

    // Update cache with the new version
    await updateObjectCache({
      model,
      id,
      object: updatedObject,
      populate,
    });

    if (model.recalculate) {
      logger.debug(`Recalculating ${model.modelName}`);
      await model.recalculate(updatedObject, user);
    }

    if (model.stats) {
      logger.debug(`Getting stats for ${model.modelName}`);
      const statsData = await model.stats();
      await distributeStats(statsData, parentType);
    }

    return updatedObject;
  } catch (error) {
    logger.error('editObject error:', error);
    return { error: error.message, code: 500 };
  }
};

// Reusable function to create a new object
export const newObject = async ({ model, newData, user = null }, distributeChanges = true) => {
  try {
    const parentType = model.modelName ? model.modelName : 'unknown';

    const result = await model.create(newData);
    if (!result || result.length === 0) {
      return { error: 'No object created.', code: 500 };
    }
    const created = expandObjectIds(result.toObject());

    await newAuditLog(newData, created._id, parentType, user);
    if (distributeChanges == true) {
      await distributeNew(created, parentType);
    }
    await distributeChildNew(created, created._id, model);

    // Cache the newly created object
    await updateObjectCache({
      model,
      id: created._id,
      object: created,
      populate: [],
    });

    if (model.recalculate) {
      logger.debug(`Recalculating ${model.modelName}`);
      await model.recalculate(created, user);
    }

    if (model.stats) {
      logger.debug(`Getting stats for ${model.modelName}`);
      const statsData = await model.stats();
      await distributeStats(statsData, parentType);
    }

    return created;
  } catch (error) {
    logger.error('newObject error:', error);
    return { error: error.message, code: 500 };
  }
};

// Reusable function to delete an object by ID, with audit logging and distribution
export const deleteObject = async ({ model, id, user = null }, distributeChanges = true) => {
  try {
    const parentType = model.modelName ? model.modelName : 'unknown';
    // Delete the object
    const result = await model.findByIdAndDelete(id);

    if (!result) {
      return { error: `${parentType} not found.`, code: 404 };
    }

    const deleted = expandObjectIds(result.toObject());
    // Audit log the deletion
    await deleteAuditLog(deleted, id, parentType, user, 'delete');

    if (distributeChanges == true) {
      await distributeDelete(deleted, parentType);
    }

    await distributeChildDelete(deleted, id, model);

    // Invalidate cache for this object
    await deleteObjectCache({ model, id });

    if (model.recalculate) {
      logger.debug(`Recalculating ${model.modelName}`);
      await model.recalculate(deleted, user);
    }

    if (model.stats) {
      logger.debug(`Getting stats for ${model.modelName}`);
      const statsData = await model.stats();
      await distributeStats(statsData, parentType);
    }

    return { deleted: true, object: deleted };
  } catch (error) {
    logger.error('deleteObject error:', error);
    return { error: error.message, code: 500 };
  }
};

export const flushFile = async ({ id, user }) => {
  try {
    logger.info(`Starting file deletion process for file ID: ${id}`);

    // First, check if the file exists
    const file = await fileModel.findById(id).lean();
    if (!file) {
      logger.warn(`File with ID ${id} not found`);
      return {
        error: 'File not found',
        code: 404,
      };
    }

    logger.info(`Found file: ${file.name} (${file._id})`);

    // Check if this file has any dependencies
    const dependencies = await listObjectDependencies({
      model: fileModel,
      id: file._id,
    });

    if (dependencies.length > 0) {
      logger.info(
        `File ${file._id} (${file.name}) has ${dependencies.length} dependencies, cannot delete`
      );
      return {
        error: 'File has dependencies and cannot be deleted',
        code: 409,
        dependencies: dependencies.length,
        dependencyDetails: dependencies,
      };
    }

    logger.debug(`File ${file._id} (${file.name}) has no dependencies, proceeding with deletion`);

    // Delete from database first
    const deleteResult = await deleteObject({
      model: fileModel,
      id: file._id,
      user,
    });

    if (deleteResult.error) {
      logger.error(`Failed to delete file ${file._id} from database:`, deleteResult.error);
      return {
        error: deleteResult.error,
        code: deleteResult.code || 500,
      };
    }

    // Try to delete from Ceph storage if it exists
    if (file.extension) {
      try {
        const { deleteFile } = await import('../services/storage/ceph.js');
        const { BUCKETS } = await import('../services/storage/ceph.js');
        const cephKey = `files/${file._id}${file.extension}`;

        await deleteFile(BUCKETS.FILES, cephKey);
        logger.debug(`Deleted file from Ceph storage: ${cephKey}`);
      } catch (cephError) {
        logger.warn(`Failed to delete file ${file._id} from Ceph storage:`, cephError.message);
        // Don't treat Ceph deletion failure as a critical error since DB record is already deleted
      }
    }

    const result = {
      success: true,
      deletedFile: {
        fileId: file._id,
        fileName: file.name,
        deletedAt: new Date(),
      },
    };

    logger.info(`Successfully deleted file: ${file.name} (${file._id})`);
    return result;
  } catch (error) {
    logger.error('Error in flushFile:', error);
    return {
      error: error.message,
      code: 500,
    };
  }
};

// Helper function to recursively delete objects and their children
export const recursivelyDeleteChildObjects = async (
  { model, id, user = null },
  distributeChanges = true
) => {
  const deletedIds = [];

  // Find all objects that have this object as their parent
  const childObjects = await model.find({ parent: id });

  // Recursively delete all children first
  for (const childObject of childObjects) {
    const childDeletedIds = await recursivelyDeleteChildObjects(
      { model, id: childObject._id, user },
      false
    );
    deletedIds.push(...childDeletedIds);
  }

  // Delete the current object

  await deleteObject({ model, id, user }, distributeChanges);

  deletedIds.push(id);

  return deletedIds;
};
