import mongoose from 'mongoose';
import Joi from 'joi';
import { v4 as uuidV4 } from 'uuid';

export type TDictionary = Record<string, string | object>;

export const fieldsToUnset = ['__v', 'createdAt', 'updatedAt'];

const deepCloneAndConvertMaps = <T>(obj: T): T => {
  if (obj instanceof Map) {
    return Object.fromEntries(obj) as T;
  }
  if (Array.isArray(obj)) {
    return obj.map((item) => deepCloneAndConvertMaps(item)) as T;
  }
  if (Buffer.isBuffer(obj) || ArrayBuffer.isView(obj)) {
    return obj;
  }
  if (obj instanceof Date) {
    return new Date(obj.getTime()) as T;
  }
  if (obj instanceof mongoose.Types.ObjectId) {
    return obj.toString() as T;
  }
  if (typeof obj === 'object' && obj !== null) {
    const newObj = Object.assign({}, obj);
    Object.keys(newObj).forEach((key) => {
      newObj[key] = deepCloneAndConvertMaps(newObj[key]);
    });
    return newObj as T;
  }
  return obj;
};

export const removeFields = <T>(document: mongoose.Document, fieldsToUnset: string[]): T => {
  return document.toObject({
    transform: (_doc, ret) => {
      fieldsToUnset.forEach((field) => delete ret[field]);
      return deepCloneAndConvertMaps(ret) as unknown as Omit<T, keyof T>;
    },
  });
};

export const replaceErrors = (error: Joi.ValidationError, dictionary: TDictionary) => {
  const getValue = (d: TDictionary, keys: string[]): string => {
    let currentValue: TDictionary | string = d;

    for (const part of keys) {
      if (currentValue && currentValue[part]) {
        currentValue = currentValue[part];
      } else {
        return part;
      }
    }
    return currentValue as string;
  }

  const refinedErrors = error.details.map((detail) => {
    const pathFull = detail.path.join('.');
    const value = getValue(dictionary, detail.path as string[]);
    return detail.message.replace(pathFull, value);
  });
  return refinedErrors.join(', ');
};

export const getUUID = (): string => uuidV4();
