
const {
  formatError,
  encryptPassword,
  cleanPath,
  hashPassword,
  comparePassword,
  toLowerCase,
  deepDivePassword,
  generateRandomCode,
  deepSearchCrud
} = require("../utilities/utils");

const {beforeCreateDocument} =require('../api-hooks/index')
const {updateCacheChanged,updateCachedQuery} = require('../cache/index')
const {loadModels} =  require('../models')

const fs =require('fs-extra')
//process.cwd() + "/node_modules/.prisma/client"
const _ =require('lodash')
const excludesData =['id','_id']

const extendPath = cleanPath(process.cwd() + "/database/extend");
const registerPrisma = require("" + extendPath);
const { PrismaClient, Prisma } = require("@prisma/client");
const {subscribeRealTimeCollections} =require("../socket/io")

const {validator} = require("../validator/index")




global.prisma =  new PrismaClient({

  log: [
    {
      emit: "event",
      level: "query",
    },
  ],
});

const bcrypt =require('bcrypt')



const errorModelNotFound =(model) => `Model or collection ${model} not found`.red;
const chokidar = require("chokidar");
const system = require("system-commands");
const {computedFx} =require('./computedFx');
const e = require("express");


async function validationSchema(){
  console.log('\n')
  consola.log(`[DB] Waiting , Rebuild database Schemas ....\n`.gray);

  await system('npx prisma generate && npx prisma db push').then(e=>{
    consola.log(`[DB] ✅ Database synchronized , server will restart in few seconds ... \n`.gray);
  }).catch(err=>{
    console.log(err)
  })
}

//







//

async function loadExtendPrisma(config){

  try{
    var extensions =[]
    var extendConfigs =registerPrisma({
      getOneCollection:config.getOneCollection,
      privateFields:config.privateFields,
      extensions:extensions
    })

    await computedFx(extensions,config)

    extensions.push({
      query: {
        $allModels: {
          $allOperations({ model, operation, args, query }) {

           if(operation?.toLowerCase()?.match(/(create|createmany|update|updatemany|delete|deletemany)/)){
            updateCacheChanged(model,config)
           }
            /* your custom logic for modirying all operations on all models here */
            return query(args)
          },
        },
      },
    })

    extensions.push({
      query:{
        
          $allModels:{

            async create({model,operation,args,query}){
           if(model!=='logs'){
            args = await deepDivePassword(args)
             
            var excludes = config.getPrivateFields(model)
            var fields = config.getFieldsCollection(model)

           try{
            args.data = await deepSearchCrud(model,args.data,prisma,config)
            args.data = await generateRandomCode(model, fields,args.data,prisma)
           } catch(e){

            console.log(e)
           }


           // var v =  validator(fields,args.data)
         //  if(!v.valid) throw new Error(String(v.error))

         


            var responses = await query(args)
            responses = _.omit(responses,excludes)
            return responses
           }
    
            },
            async createMany({model,operation,args,query}){
              var fields = config.getFieldsCollection(model)
              args = await deepDivePassword(args)
               if(args?.data && args.data.length){
                for await (var [i,d] of args.data?.entries()){
                  args.data[i] = await deepSearchCrud(model,args.data[i],prisma,config)
                  args.data[i] = await generateRandomCode(model, fields,d,prisma)
                }
               }
              
              return query(args)
        },

        async update({model,operation,args,query}){
              args = await deepDivePassword(args)
              var excludes = config.getPrivateFields(model)
              var fields = config.getFieldsCollection(model)
             // var v =  validator(fields,args.data)
             // if(!v.valid) throw new Error(String(v.error))
              
                var responses = await query(args)
                responses = _.omit(responses,excludes)
                return responses
        },
            async updateMany({model,operation,args,query}){
              args = await deepDivePassword(args)
              return query(args)
             },
       
              async findMany({ model, operation, args, query }) {
                var auth = args?.auth || false
                args?.auth  ? delete args.auth : null
                var excludes = config.getPrivateFields(model)
                var responses = await query(args)
                if(!auth){
                  responses = responses.map(r=>_.omit(r,excludes))
                }
                return responses
                },
                async findUnique({ model, operation, args, query }) {
                  var excludes = config.getPrivateFields(model)
                  var responses = await query(args)
                  responses = _.omit(responses,excludes)
                  return responses

                },
          }
      }
  
  })
  
    extensions.map(conf=>{
      prisma =prisma.$extends(conf)
    })
    
    //console.log("--Extend  prisma extension used--")
  
  } catch(err){
    //console.error("--Loaded extension prisma failed --")
    console.log(err)
  }

}

function logEvents (config){
  //prisma.$on("query", (e,n) => {
  //  console.log(n)
 //   console.log("Query: " + e.query);
  //  console.log("Params: " + e.params);
  //  console.log("Duration: " + e.duration + "ms");
 // });
}

function middleware(config) {

}

/**
 * 
 * @param {*} value 
 * @param {*} config 
 * @returns 
 */
async function autoUpdateDatabase(value = true ,config) {
  var prismaDir = cleanPath(process.cwd() + "/prisma/schema.prisma");
  return new Promise(function (resolve, reject) {
    if (value && process.env.NODE_ENV == "dev") {
      chokidar
        .watch(cleanPath(process.cwd() + "/prisma/schema.prisma"))
        .on("all", async (path) => {
        //  consola.log(`[DB] 🔄 Sync database `.gray);
           await system("npx prisma generate && npx prisma db push")
            .then((e, cmd) => {
             // loadModels(config)
              console.log('')
              consola.log(`[DB] ✅ Database synchronized \n`.gray);
              consola.log(`[TIME] 🕙 ${new Date()}  `);
              resolve();
            })
            .catch((err) => {
              consola.error(err);
              reject(err);
            });
        });
    }
  });
}



var api_ = {
  isSet: (model) => {
    // detect is model exixts
   let model_ = model ||'-';
   return prisma[model_] ? true : false;
  },

  /**
   * 
   * @param {*} raws 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  rawQuery: async (raws,req,config) => {
    return new Promise(async (resolve, reject) => {
      var responses = {};
      var queries = [];
      try {
        Object.keys(raws).forEach((e, i, raw) => {
          //console.log(e, i);
          queries.push({
            name: e,
            model: raws[e]?.model || null,
            action: raws[e]?.action || null,
            params: raws[e]?.params || {},
          });
        });

       
        for await (var raw of queries) {
          let model = raw?.model || null;
          if (prisma[model] && raw.name && raw.action) {
            let docs = [];
            if (raw.action == "findMany") {
              // findMany
              if(raw.params?.auth) delete raw.params.auth 
              docs = await prisma[model]
                .findMany(raw.params)
                .then((e) => {
                  return {
                    data: e,
                    meta: {
                      count: e.length,
                    },
                  };
                })
                .catch((err) => {
                  return {
                    data: [],
                    meta: {
                      count: 0,
                      error: err,
                    },
                  };
                });
             

              responses[raw.name] = docs;
            }
          } else {
            responses[raw.name] = {
              data: [],
              meta: {
                count: 0,
                error: "Model not found",
              },
            };
          }
        }

        resolve(responses);
      } catch (err) {
        consola.error(err);
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} options 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */
  findFirst: async (model = "", options, req, config = null) => {
    return new Promise(async (resolve, reject) => {
      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeFindFirst) {
          await config.hooks[toLowerCase(model)]?.beforeFindFirst({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            api: config.api,
            socket:config.state?.socket,
            io: config.state?.io,
            many: false,
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
        var doc = await prisma[model].findFirst(options).then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterFindFirst) {
          await config.hooks[toLowerCase(model)].afterFindFirst({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            api: config.api,
            doc: doc || [],
            many: false,
            socket:config.state?.socket,
            io: config.state?.io,
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
        resolve(doc);
      } catch (err) {
        consola.error(err);
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} options 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  findUnique: async (model = "", options, req, config = null) => {
    return new Promise(async (resolve, reject) => {
      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeFind) {
          await config.hooks[toLowerCase(model)]?.beforeFind({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            api: config.api,
            many: false,
            socket:config.state?.socket,
            io: config.state?.io,
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
        var doc = await prisma[model].findUnique(options).then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterFind) {
          await config.hooks[toLowerCase(model)].afterFind({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            api: config.api,
            doc: doc || [],
            socket:config.state?.socket,
            io: config.state?.io,
            many: false,
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
        resolve(doc);
      } catch (err) {
        consola.error(err);
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} options 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  auth: async (model = "", options, req, config = null) => {

    options.auth = true;
    return new Promise(async (resolve, reject) => {

      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeAuth) {
          await config.hooks[toLowerCase(model)].beforeAuth({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            socket:config.state?.socket,
            io: config.state?.io,
            api: config.api,
            services: config.service,
            state:config.state,
            user:{...req?.state?.user} || {}
          });
        }

      
        var doc = await prisma[model].findMany(options).then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterAuth) {
          await config.hooks[toLowerCase(model)].afterAuth({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            api: config.api,
            doc: doc || [],
            socket:config.state?.socket,
            io: config.state?.io,
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
        resolve(doc);
      } catch (err) {
        consola.error(err);
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} options 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  findMany: async (model = "", options, req, config = null) => {

    let useMeta = options?.useMeta
    options = _.omit(options,['useMeta','usemeta'])

    
   
    return new Promise(async (resolve, reject) => {

      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeFind) {
          await config.hooks[toLowerCase(model)].beforeFind({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            api: config.api,
            many: true,
            socket:config.state?.socket,
            io: config.state?.io,
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }


       if(useMeta){
        let optionsCount = _.omit(options, ['include','take','skip','cursor','distinct'])
        let elapsedStart = performance.now()
        var [count,doc]= await prisma.$transaction([
          prisma[model].findMany(optionsCount),
          prisma[model].findMany(options)
        ])

        let time = performance.now() - elapsedStart
        
        doc={
          meta:{
            total:count?.length||0,
            take:options?.take ||null,
            skip:options?.skip||null,
            count:doc?.length||0,
            times:{
              ms:Number(time.toFixed(2)).toFixed(2),
              s:Number(time.toFixed(2)/1000).toFixed(2),
              min:Number(time.toFixed(2)/60000).toFixed(2)
            }, //seconds
            memoryUsage:Number((process.memoryUsage().heapTotal/1024/1024).toFixed(4)).toFixed(2) // MB
          },
          data:doc
        }
       } else {
        var doc = await prisma[model].findMany(options).then((e) => e);
       }

        if (config?.hooks[toLowerCase(model)]?.afterFind) {
          await config.hooks[toLowerCase(model)].afterFind({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            socket:config.state?.socket,
            io: config.state?.io,
            api: config.api,
            doc: doc || [],
            many: true,
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
        resolve(doc);
      } catch (err) {
        
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} options 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  
  findType: async (model = "", options, req, config = null) => {
    return new Promise(async (resolve, reject) => {

      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeFind) {
          await config.hooks[toLowerCase(model)].beforeFind({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            many:false,
            socket:config.state?.socket,
            io: config.state?.io,
            api: config.api,
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
        var doc = await prisma[model].findMany({
          take:1,
        }).then((e) => e[0] || {});

        if (config?.hooks[toLowerCase(model)]?.afterFind) {
          await config.hooks[toLowerCase(model)].afterFind({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            socket:config.state?.socket,
            io: config.state?.io,
            api: config.api,
            doc: doc || [],
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
        resolve(doc);
      } catch (err) {
        consola.error(err);
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} data 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  create: async (model, data, req, config = null) => {
    return new Promise(async (resolve, reject) => {
      try {

       
      
       
        
        if (!prisma[model]) return reject(errorModelNotFound(model));
        //console.log('BBB',config?.hooks[model.toLowerCase()])

        beforeCreateDocument(model,data,config,"one")
        if (config?.hooks[toLowerCase(model)] && config.hooks[toLowerCase(model)]?.beforeCreate) {
          await config.hooks[toLowerCase(model)]?.beforeCreate({
            model: model,
            input: data,
            req: req,
            api: config.api,
            many: false,
            socket:config.state?.socket,
            io: config.state?.io,
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }

        var doc = await prisma[model].create(data).then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterCreate) {
          await config.hooks[toLowerCase(model)]?.afterCreate({
            model: model,
            input: data,
            req: req,
            api: config.api,
            state:config.state,
            socket:config.state?.socket,
            io: config.state?.io,
            doc: doc || [],
            services: config.service,
            many:false,
            user:{...req?.state?.user} || {}
          });
        }

        subscribeRealTimeCollections(config,model,doc,'createOne')
       // updateCacheChanged(model)
        resolve(doc);
      } catch (err) {
        console.log(err);
        reject(formatError(err));
      }
    });
  },

  /**
   * 
   * @param {*} model 
   * @param {*} data 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  createMany: async (model, data, req = null, config = null) => {
    return new Promise(async (resolve, reject) => {
      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeCreate) {
          await config.hooks[toLowerCase(model)].beforeCreate({
            model: model,
            input: data,
            req: req,
            state:config.state,
            api: config.api,
            socket:config.state?.socket,
            io: config.state?.io,
            many: true,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
        beforeCreateDocument(model,data,config,"many")
        var doc = await prisma[model].createMany(data).then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterCreate) {
          await config.hooks[toLowerCase(model)].afterCreate({
            model: model,
            input: data,
            req: req,
            api: config.api,
            state:config.state,
            socket:config.state?.socket,
            io: config.state?.io,
            doc: doc || [],
            services: config.service,
            many:true,
            user:{...req?.state?.user} || {}
          });
        }
        subscribeRealTimeCollections(config,model,doc,'createMany')
        //updateCacheChanged(model) 
        resolve(doc);
      } catch (err) {
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} data 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  update: async (model, data, req, config = null) => {
    return new Promise(async (resolve, reject) => {
      try {
        data?.data ? data.data  = _.omit(data.data,excludesData):null
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeUpdate) {
          await config.hooks[toLowerCase(model)].beforeUpdate({
            model: model,
            input: data,
            state:config.state,
            req: req,
            api: config.api,
            many: false,
            socket:config.state?.socket,
            io: config.state?.io,
            services: config.service,
            user:{...req?.state?.user} || {},
          });
        }

        var doc = await prisma[model].update(data).then((e) => e);
     
        if (config?.hooks[toLowerCase(model)]?.afterUpdate) {
          await config.hooks[toLowerCase(model)].afterUpdate({
            model: model,
            input: data,
            req: req,
            socket:config.state?.socket,
            io: config.state?.io,
            api: config.api,
            doc: doc || [],
            state:config.state,
            many: false,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }

        subscribeRealTimeCollections(config,model,doc,'updateOne')
      //  updateCacheChanged(model)
        resolve(doc);
      } catch (err) {
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} data 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */
  updateType: async (model, data, req, config = null) => {
    
    return new Promise(async (resolve, reject) => {
      try {
        data?.data ? data.data  = _.omit(data.data,excludesData):null
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeUpdate) {
          await config.hooks[toLowerCase(model)].beforeUpdate({
            model: model,
            input: data,
            socket:config.state?.socket,
            io: config.state?.io,
            req: req,
            api: config.api,
            many:false,
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {},
          });
        }

        var getItem = await prisma[model].findMany({take:1}).then(e=>e)
         
       if(getItem.length){
      
        data.where ={
          id:getItem[0]?.id
         }

        

        var doc = await prisma[model].update(data).then((e) => e);
       } else {
        var doc = await prisma[model].create(data)
       }

        if (config?.hooks[toLowerCase(model)]?.afterUpdate) {
          await config.hooks[toLowerCase(model)].afterUpdate({
            model: model,
            input: data,
            req: req,
            socket:config.state?.socket,
            io: config.state?.io,
            api: config.api,
            doc: doc || [],
            state:config.state,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
       // updateCacheChanged(model)
        subscribeRealTimeCollections(config,model,doc,'updateType')
        resolve(doc);
      } catch (err) {
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} data 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */
  updateMany: async (model, data, req = null, config = null) => {
    return new Promise(async (resolve, reject) => {
      try {
        
        data?.data ? data.data  = _.omit(data.data,excludesData):null
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeUpdate) {
          await config.hooks[toLowerCase(model)].beforeUpdate({
            model: model,
            input: data,
            req: req,
            state:config.state,
            api: config.api,
            socket:config.state?.socket,
            io: config.state?.io,
            many: true,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }


        var doc = await prisma[model].updateMany(data).then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterUpdate) {
          await config.hooks[toLowerCase(model)].afterUpdate({
            model: model,
            input: data,
            query: { ...options },
            socket:config.state?.socket,
            io: config.state?.io,
            req: req,
            api: config.api,
            doc: doc || [],
            state:config.state,
            many: true,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
       // updateCacheChanged(model)
        subscribeRealTimeCollections(config,model,doc,'updateMany')
        resolve(doc);
      } catch (err) {
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} options 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */
  delete: async (model, options, req, config) => {
    return new Promise(async (resolve, reject) => {
      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeDelete) {
          await config.hooks[toLowerCase(model)].beforeDelete({
            model: model,
            options: options,
            query: { ...options },
            state:config.state,
            socket:config.state?.socket,
            io: config.state?.io,
            req: req,
            api: config.api,
            many: false,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }

        var doc = await prisma[model].delete(options).then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterDelete) {
          await config.hooks[toLowerCase(model)].afterDelete({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            api: config.api,
            state:config.state,
            many:false,
            socket:config.state?.socket,
            io: config.state?.io,
            doc: doc || [],
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
       // updateCacheChanged(model)
        subscribeRealTimeCollections(config,model,doc,'deleteOne')
        resolve(doc);
      } catch (err) {
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} options 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  deleteMany: async (model, options, req, config = null) => {
    return new Promise(async (resolve, reject) => {
      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeDelete) {
          await config.hooks[toLowerCase(model)].beforeDelete({
            model: model,
            options: options,
            query: { ...options },
            state:config.state,
            req: req,
            api: config.api,
            socket:config.state?.socket,
            io: config.state?.io,
            many: true,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }

        var doc = await prisma[model].deleteMany(options).then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterDelete) {
          await config.hooks[toLowerCase(model)].afterDelete({
            model: model,
            options: options,
            req: req,
            api: config.api,
            socket:config.state?.socket,
            io: config.state?.io,
            many:true,
            state:config.state,
            doc: doc || [],
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }
       // updateCacheChanged(model)
        subscribeRealTimeCollections(config,model,doc,'deleteMany')
        resolve(doc);

      } catch (err) {
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} options 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  findRaw: async (model, options, req, config = null) => {
    return new Promise(async (resolve, reject) => {
      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeFindRaw) {
          await config.hooks[toLowerCase(model)].beforeFindraw({
            model: model,
            options: options,
            query: { ...options },
            state:config.state,
            req: req,
            socket:config.state?.socket,
            io: config.state?.io,
            api: config.api,
            many: true,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }

        var doc = await prisma[model].findRaw(options).then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterFindRaw) {
          await config.hooks[toLowerCase(model)].afterFindRaw({
            model: model,
            options: options,
            req: req,
            api: config.api,
            socket:config.state?.socket,
            io: config.state?.io,
            many:true,
            state:config.state,
            doc: doc || [],
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }

        resolve(doc);
      } catch (err) {
        reject(err);
      }
    });
  },
  groupBy: async (model, input, req, config) => {
    return new Promise(async (resolve, reject) => {
      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeGroupBy) {
          await config?.hooks[toLowerCase(model)]?.beforeGroupBy({
            model: model,
            pipelineOptions: pipelineOptions,
            query: { ...pipelineOptions },
            req: req,
            state:config.state,
            api: config.api,
            socket:config.state?.socket,
            io: config.state?.io,
            user:{...req?.state?.user} || {},
            services: config.service,
          });
        }
        var doc = await prisma[model]
          .groupBy(input)
          .then((e) => e);
        if (config?.hooks[toLowerCase(model)]?.afterGroupBy) {
          await config?.hooks[toLowerCase(model)]?.afterGroupBy({
            model: model,
            pipelineOptions: pipelineOptions,
            query: { ...pipelineOptions },
            req: req,
            socket:config.state?.socket,
            io: config.state?.io,
            state:config.state,
            api: config.api,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }

        resolve(doc);
      } catch (err) {
        console.log(err)
        reject(err);
      }
    });
  },
  /**
   * 
   * @param {*} model 
   * @param {*} pipelineOptions 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */


  aggregateRaw: async (model, pipelineOptions, req, config) => {
    return new Promise(async (resolve, reject) => {
      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeAggregateRaw) {
          await config?.hooks[toLowerCase(model)]?.beforeAggregateRaw({
            model: model,
            pipelineOptions: pipelineOptions,
            query: { ...pipelineOptions },
            req: req,
            socket:config.state?.socket,
            io: config.state?.io,
            state:config.state,
            api: config.api,
            user:{...req?.state?.user} || {},
            services: config.service,
          });
        }
        var doc = await prisma[model]
          .aggregateRaw({
            pipeline:pipelineOptions
          })
          .then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterAggregateRaw) {
          await config?.hooks[toLowerCase(model)]?.afterAggregateRaw({
            model: model,
            pipelineOptions: pipelineOptions,
            query: { ...pipelineOptions },
            req: req,
            socket:config.state?.socket,
            io: config.state?.io,
            state:config.state,
            api: config.api,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }

        resolve(doc);
      } catch (err) {
        console.log(err)
        reject(err);
      }
    });
  },

  /**
   * 
   * @param {*} model 
   * @param {*} options 
   * @param {*} req 
   * @param {*} config 
   * @returns 
   */

  aggregate: async (model, options, req, config) => {
    return new Promise(async (resolve, reject) => {
      try {
        if (!prisma[model]) return reject(errorModelNotFound(model));
        if (config?.hooks[toLowerCase(model)]?.beforeAggregate) {
          await config?.hooks[toLowerCase(model)]?.beforeAggregate({
            model: model,
            options: options,
            query: { ...options },
            socket:config.state?.socket,
            io: config.state?.io,
            req: req,
            state:config.state,
            api: config.api,
            user:{...req?.state?.user} || {},
            services: config.service,
          });
        }
        var doc = await prisma[model]
          .aggregate(options)
          .then((e) => e);

        if (config?.hooks[toLowerCase(model)]?.afterAggregate) {
          await config?.hooks[toLowerCase(model)]?.afterAggregate({
            model: model,
            options: options,
            query: { ...options },
            req: req,
            socket:config.state?.socket,
            io: config.state?.io,
            state:config.state,
            api: config.api,
            services: config.service,
            user:{...req?.state?.user} || {}
          });
        }

        resolve(doc);
      } catch (err) {
        console.log(err)
        reject(err);
      }
    });
  },
};



api_.createOne =api_.create
api_.deleteOne = api_.delete
api_.updateOne =api_.update
api_.findOne=api_.findUnique

const api = api_



module.exports = {
  api,
  prisma,
  autoUpdateDatabase,
  prismaMiddleware: middleware,
  loadExtendPrisma,
  logEvents,
  validationSchema
};
