require("dotenv");
const {updateModelViews,apiModelviews} =require('../modeviews')
const {execServices} = require("../services")
const {sessionJwt,cacheKeys} =require('../cache/index')
const Datastore = require('@seald-io/nedb')
const moment = require('moment')
const boxen =require('boxen')
const nodecache =require('node-cache')
const {StartModeCluster}=require('./cluster')
const appCache = new nodecache({ stdTTL : 60*60 * 24}); // 1h

const {v4} = require('uuid');
  

const fs = require("fs");

const system = require('system-commands')


const {canResolveCache,updateCachedQuery} = require('../cache/index')
const cleanDeep = require("clean-deep")
const {saveLog} =require("../logs")
const {policiesWhere} = require('../policies')
const express =require("express")
const {formatError ,privateFields,comparePassword,isAlreadyHashed,setNedbFilter,formatToJson} = require('../utilities/utils')
//const cors = require("cors")
const { api } = require('../api/prisma')
const _ =require("lodash")
const session = require("express-session");
const { authenticate, generateToken } = require('../auth')
const ipfilter = require('express-ipfilter').IpFilter
const {unlinkMedia} =require('../upload/index');
const {filterDataOnComputed} = require('../compted')

const { query } = require("express");
const {applyCollections} =require('../collections/index.js')
const errorHandler = (error, request, response, next) =>{
  // Error handling middleware functionality
  console.log( `error ${error.message}`) // log the error
  const status = error.status || 400
  // send back an easily understandable error message to the caller
  response.status(status).send(error.message)
}
function loadRoute(config,appInstance) {
  var app = config.app; // Express app instance
  //app.use(errorHandler)
  //app.use(
    // Use session
   // session({
    //  secret: "keyboard cat",
    //  resave: false,
    //  saveUninitialized: true, 
    //  cookie: { secure: true },
  //  })
//  );   






  
  if (config.debug) {
    
  }

  if(config.express?.whiteListIps?.length ){ // white list IP
    app.use(ipfilter(config.express.whiteListIps,{mode:'allow'}))
   
  }

  if(config.express?.blackListIps?.length ){ // white list IP
    app.use(ipfilter(config.express.blackListIps,{mode:'deny'}))
    
  }


  //console.log("cors",config.express.corsOptions)
  //appInstance.use(cors(config.express.corsOptions))



  // Root utils
  app.post('/__CTRL__UTILS__',async(req,res)=>{
    if(req.query.updateRootCollections){ 

      if(process.env.NODE_ENV=='production'){
        return send.send({ok:true})
      } else {
        var collections = req.body.collections; 
        applyCollections(collections,config)
        return res.send({ok:true})
      }

     
    }

  

    return res.status(403).send({
      message:"Forbidden"
    })

  })

  app.get('/erd-diagram',(req,res)=>{
    try{
      res.sendFile(process.cwd()+'/prisma/models/ERD.svg')
    } catch(err){
      res.status(400)
    }
  })

 
  // Root utils 
  app.get('/__CTRL__UTILS__',async(req,res,next)=>{
    if(req.query.getCollections){ 
      return res.send(config.collections.schemas.sort((a,b)=>a?.slug?.toLowerCase()<b?.slug?.toLowerCase()?-1:1))
    }

    if(req.query.getConfig){
      var settings = await api.findType('settings',{},req,config).then(e=>e).catch(()=>{})
       var config_ = { // config to send 
         collections: config.collections.schemas.sort((a,b)=>a?.slug?.toLowerCase()<b?.slug?.toLowerCase()?-1:1),
         settings:settings,
         version:config.package.version,
         production:process.env.NODE_ENV=='production' ? true : false
        // insights,
        // apps

       }
      return res.send(config_)
 
     }

    res.status(404).send({
      message:"Not found"
    })

  })


  app.get(`/v1/modelviews/:model`,async(req,res,next)=>{
   await  apiModelviews(config,req,res,next)

  })



  app.post(`/v1/services/:serviceName`,async(req,res,next)=>{
    var serviceName  = req.params?.serviceName || null
    var params  = req?.body ||null
    await  execServices({
      serviceName: serviceName,
      params:params,
      req:req,
      res:res,
      config:config,
      next:next
    })
   })

   app.post(`/v1/caches/:name`,async(req,res,next)=>{

    if(!config?.useSystemCache)  return res.status(404).send({
      error:'Cache system not enabled'
    })

    var params  = req.body ||{}
    params =cleanDeep(params)
    var nameOfCache = req?.params?.name
    
    if(req.params?.name=='set' && params?.key){
      let setData  ={
        data:params.value,
        cachedAt: moment().format()
      }
      config.caches.set(params.key,JSON.stringify(setData),params?.expire||0)
      return res.send(setData)
    }

    if(req.params?.name=='get' && params?.key){
     if(config.caches.has(params.key)){
      var response = config.caches.get(params?.key)
      response = JSON.parse(response)
      return res.send(response)
     } else {
      return res.status(400).send('Key not exist')
     }
    }

   
    
    if(!req.params?.name.match(/(set|get)/)){
      if(!config.caches.has(`vcaches:${nameOfCache}`)){
        return res.status(400).send('Key not exist')
      }
      
      var response =  config.caches.get(`vcaches:${nameOfCache}`)
      var dbCache = new Datastore()
      try{
        response = JSON.parse(response)
      } catch(err){
        console.log(err)
      }

      try{
        if(!_.isEmpty(params) && response){
          
          if(Object.keys(params?.filter||{}).length){
            response = response.map(e=>({
              ...e,
              _id:e.id
            }))
          await dbCache.insertAsync(response)
          response = await dbCache.findAsync(setNedbFilter(params?.filter)||{})
          dbCache.removeAsync({},{multi:true})
          }
        }
       return response ? res.send(response) : res.status(400).send({
        error:`key ${nameOfCache}  no exist`
       }) 
      } catch(err){
        console.log(err)
        res.status(400).send(err)
      }
    } else {
      return res.status(400).send({
        error:'Bad request'
      })
    }
      
   
   })

   app.use(`${config.routes.api}/:model`,async(req,res,next)=>{
    let model = req.params?.model|| ''
   


    if(config?.policies[model]){

      let options_={
        canNext:true,
        statusCode:null,
        sendData:null,
            status(val){
              this.canNext =false
              this.statusCode=val
              return this
            },
            send(val){
              this.canNext =false
              this.sendData=val
              return this
            },
            next(){
              this.canNext =true
            }
      }

  

   
    
     let response =  await config.policies[model]({
        req:req,
        res:options_,
        next:options_.next,
        api:config.api,
        state:config.state,
        services: config.service,
        user:{...req?.state?.user} || {}
      })


 
      
      if(options_.canNext){
        next()
      } else {
        res.status(options_.statusCode||400).send(options_.sendData||null)
      }



      
    } else {

      next()

    }

   

   })


  // routes all api rest
  // Get api routes
  app.get(`${config.routes.api}/:model`, async (req, res, next) => {
    // get all record
    try {
      var response ;
      var model = req.params?.model|| ''
      let _findMany_ = req.query?.findMany || null
      let _findUnique_ = req.query?.findOne || null
      let _findType_ = req.query?.findType || null
      let _auth_ = req.query?.auth || null
      let _options_ = req.query?.options || {};
      


  if(_auth_){ // AUTH

   

   
           // auth request
    let model = req.params.model.toLowerCase() || null
    let vz ={
            auth:false,
            isUsers:false,
            isRoot:false,
            model:model,
            user:{},
            id:null
            
    }


    try {

      if(model=="users"){
    
        vz.isUsers = true // Internal Admin Users
        if(_options_.where?.email==process.env.ADMIN_EMAIL 
         && 
         _options_.where?.password==process.env.ADMIN_PASSWORD){
           vz.isRoot = true; // if super Root
           vz.auth = true; // Authentication OK
        } else { // If not super root
   
        }
   
       } 



       if(!vz.isRoot){

      

        var col = config?.getOneCollection(req.params.model) // get Collection
       
    
        if(!col || !col?.auth?.enabled){
         return res.status(400).send({
          error:"Authentication not enabled"
         })
        }

       

        console.log(_options_,_options_.where?.[col.auth.by])
 
 
        if(!_options_.where?.[col.auth.by]|| !_options_.where?.password || _options_.where?.password=="" ){
 
        return res.status(400).send({
           error:`${col.auth.by} or password is/are empty`
         });
        }

        //console.log("Value is",config.formatValue(col.slug,col.auth.by,_options_.where?.[col.auth.by]))


        var buildInclude = {
          include:{
            ..._options_.include ,
            role:true
          }
        }

       
      
        var response = await api
        .auth(req.params.model, {
          where:{
            [col.auth.by]:config.formatValue(col.slug,col.auth.by,_options_.where?.[col.auth.by]),
           // password:_options_.where?.password ,
            ...cleanDeep(col.auth?.where||{})
          },
          ...cleanDeep(buildInclude)
          
        }, req, config)
        .then((e) => e).catch(err=>{
          console.log(err)
          return []
        });

        if(response.length){


          // Comapred hashed password
          var Auth_ =  await comparePassword(_options_.where?.password,response[0]?.password).then(e=>e).catch(err=>false)
          if(isAlreadyHashed(response[0].password) && !Auth_){
              return  res.status(401).send({
                auth:false,
                error: 'Authentication failed',
              });
          } 

          if(!isAlreadyHashed(response[0].password) && response[0].password!==_options_.where?.password){
            return  res.status(401).send({
              auth:false,
              error: 'Authentication failed',
            });

          }

          var pick =[]
          Object.keys(buildInclude.include).map(key=>{
            pick.push(key)
          })
          pick.push('id')
          pick.push('role')

         
          vz.auth =true
          vz.user = _.pick(response[0],pick)
          vz.id = response[0]?.id

          saveLog({
            type:'api',
            action:'auth',
            user:req.state?.user,
            model:req.params?.model,
            details:{
              input:_options_,
              auth:true
            }
           })
        }
       }

   if(vz.auth){
        var jwt = generateToken({
        data:{
          role:_.pick(vz.user?.role) || null,
          isRoot:vz.isRoot,
          ...vz,
          clientIp:req.clientIp || null,
          authModel:req.params?.model || 'Users' || null,
         // permissions:_.defaultsDeep({
          //  ...acl.permissions||{},
           // ...config.collections.permissions
        //  }),
        },
        config: config,
        exp: "7d",
        })
      
        var TTL_DOC = await cacheKeys.insertAsync({jwt:jwt})
        var _data_ = {
          authModel:req.params?.model,
          auth: vz.auth,
          user: vz.isRoot ? 'root':_.omit(response[0],['password']),
          jwt: TTL_DOC._id
        };
      // res.cookie("JWT",_data_.jwt)
       return res.send(_data_);
       } else {
        saveLog({
          type:'api',
          action:'auth',
          user:req.state?.user,
          model:req.params?.model,
          details:{
            input:_options_,
            auth:false
          }
         })
        return  res.status(401).send({
          auth:false,
          error: 'Authentication failed',
        });
       }
    } catch (err) {
      consola.error(err);
     return res.status(404).send({
        error:err.message||err
      });
    }
    }

    if(_findType_){
    
      response = await api
      .findType(req.params.model, _options_, req, config)
      .then((e) => e)
    }
    
 
    
    if(_findUnique_){
      policiesWhere(req.state.user,'findOne',model,_options_)
      var keyCache = String(model)+'-'+String(JSON.stringify(req.query)+JSON.stringify(_options_))
      

      if(req.query.useCache && appCache.has(keyCache)){
        response = appCache.get(keyCache)
      } else {
        response = await api
            .findUnique(req.params.model, _options_, req, config)
            .then((e) => e);
            appCache.set(keyCache,response)
      }
      
    }
           
      response = await privateFields(model,response,config,_findMany_ ? 'MANY':'SINGLE').then(e=>e).catch(err=>[])
     return res.send(response);
    } catch (err) {
      consola.error(err);
     return res.status(404).send({
        error:err.message||err
      });
    }
  });






  // Post pi routes
  app.post(`${config.routes.api}/:model`, async (req, res, next) => {
    // get all record


    try {
      var model = req.params?.model || null
      var response; 

      if(req.query.aggregateRaw){
         let pipeline =req.body?.pipeline || []
         response = await api.aggregateRaw(model,pipeline,req,config)
        return  res.send(response);
      }

      if(req.query.groupBy){
        let input =req.body?.input || []
        response = await api.groupBy(model,input,req,config)
        return  res.send(response);
      }

      
      if(req.query.findRaw){
        let args = req.body.args
        response = await api.findRaw(model,args,req,config)
        return res.send(response)

      }

      if(req.query.rawQuery){
        var raws =req.body?.raws || []
        response = await api.rawQuery(raws,req,config)
        return  res.send(response);
      }
    
      let _createMany_ = req.query?.createMany ||null;
      let _createUnique_ = req.query?.createOne ||null;
      let _data_ = req.body || {}; // json data
      let _findMany_ = req.query?.findMany || null
      let _options_ = _data_
      let _count_ = req.query?.count || null


      if(_count_){

        if(req.query?.useMeta){
            _options_.useMeta =true
        }
        _options_?.auth ? delete _options_.auth : null
       // policiesWhere(req.state.user,'findMany',model,_options_)
        var keyCache = String(model)+'-'+String(JSON.stringify(req.query)+JSON.stringify(req.body)+JSON.stringify(_options_))
        if(appCache.has(keyCache) && canResolveCache(model,config) && req.query?.useCache){
          response = appCache.get(keyCache)
        } else {
        response = await api
        .count(req.params.model, _.omit(_options_,['filterOnComputed']), req, config)
        .then((e) => e)
          updateCachedQuery(model)
          appCache.set(keyCache,response)
        }

        if(!_.isEmpty(cleanDeep(_options_?.filterOnComputed))){
          response = await filterDataOnComputed({
            data:response,
            filter:_options_,
            config:config,
            model:req.params?.model
          })

        }
       
      }
     
      if(_findMany_){

        if(req.query?.useMeta){
            _options_.useMeta =true
        }

        _options_?.auth ? delete _options_.auth : null
        policiesWhere(req.state.user,'findMany',model,_options_)
        var keyCache = String(model)+'-'+String(JSON.stringify(req.query)+JSON.stringify(req.body)+JSON.stringify(_options_))
        if(appCache.has(keyCache) && canResolveCache(model,config) && req.query?.useCache){
          response = appCache.get(keyCache)
        } else {
        
        response = await api
        .findMany(req.params.model, _.omit(_options_,['filterOnComputed']), req, config)
        .then((e) => e)
          updateCachedQuery(model)
          appCache.set(keyCache,response)
        }

        if(!_.isEmpty(cleanDeep(_options_?.filterOnComputed))){
          response = await filterDataOnComputed({
            data:response,
            filter:_options_,
            config:config,
            model:req.params?.model
          })

        }
       
      }
     
  
   if(_createMany_){
    response = 
    await api
         .createMany(req.params.model, _data_, req, config)
         .then((e) => e)

         saveLog({
          type:'api',
          action:'createMany',
          user:req.state?.user,
          model:req.params?.model,
          details:{
            input:_data_
          }
         })
   }
  
   if(_createUnique_){
  
   
    response =await api
            .create(req.params.model, _data_, req, config)
            .then((e) => e);

         saveLog({
          type:'api',
          action:'createOne',
          user:req.state?.user,
          model:req.params?.model,
          details:{
            input:_data_
          }
         })
   }


   // Sync All thing
   updateModelViews(model,config)

            response = await privateFields(model,response,config,_createMany_ ? 'MANY':'SINGLE').then(e=>e).catch(err=>[])
            res.send(response);
     
    } catch (err) {

      console.log(err)

      //
      res.status(404).send({
        error:err.message||err
      });
    }
  });

 
  

  // Post pi routes
  app.put(`${config.routes.api}/:model`, async (req, res, next) => {
    // Update




    try {
      var response;
      var model = req.params?.model || null
      let _updateMany_ = req.query?.updateMany ||null;
      let _updateUnique_ = req.query?.updateOne ||null;
      let _updateType_ = req.query?.updateType ||null;
      let _data_ = req.body || {}; // json data

      
    
      if(_data_?.data){
          _data_.data =_.omit(_data_.data,['id'])
      }


     if(_updateMany_){
      policiesWhere(req.state.user,'updateMany',model,_data_)
      var response =  await api
      .updateMany(req.params.model, _data_, req, config)
      .then((e) => e)

      saveLog({
        type:'api',
        action:'updateMany',
        user:req.state?.user,
        model:req.params?.model,
        details:{
          input:_data_
        }
       })
     }

     if(_updateType_){
      var response =  await api
      .updateType(req.params.model, _data_, req, config)
      .then((e) => e)

      saveLog({
        type:'api',
        action:'updateType',
        user:req.state?.user,
        model:req.params?.model,
        details:{
          input:_data_
        }
       })

      if(model.toLowerCase()=='settings'){ // If update setttings

        //console.log(config.state)
        config.state.socket.emit('syncConfigs',true)
      }
     }
       
     if(_updateUnique_){
        policiesWhere(req.state.user,'updateOne',model,_data_)
        response = await api
        .update(req.params.model, _data_, req, config)
        .then((e) => e);

        saveLog({
          type:'api',
          action:'createOne',
          user:req.state?.user,
          model:req.params?.model,
          details:{
            input:_data_
          }
         })

       }
      response = await privateFields(model,response,config,_updateMany_ ? 'MANY':'SINGLE').then(e=>e).catch(err=>[])
      
      
    
    // Sync All thing
    updateModelViews(model,config)

      res.send(response);
    } catch (err) {
      //

      console.log(err)

      res.status(404).send({
        error:err.message||err
      });
    }
  });

  app.delete(`${config.routes.api}/:model`, async (req, res, next) => {
    // get all record

    try {
     
      var model =req.params.model
      var response;
      let _deleteMany_ = req.query?.deleteMany ||null
      let _deleteUnique_ = req.query?.deleteOne ||null
     
  
      let _options_ = req.query.options;


      // If is medias want to delete
      var mediasToRemove = []
 if(req.params.model.toLowerCase()=='medias'){
    mediasToRemove= await api.findMany("medias",_options_,req,config).then(e=>e)
  }
  
    //  console.log(_options_);
   if(_deleteMany_){


    policiesWhere(req.state.user,'deleteMany',model,_options_)



    response =
    await api
         .deleteMany(req.params.model, _options_, req, config)
         .then((e) => e)
         saveLog({
          type:'api',
          action:'deleteMany',
          user:req.state?.user,
          model:req.params?.model,
          details:{
            input:_options_
          }
         })
   }

   if(_deleteUnique_){
    policiesWhere(req.state.user,'deleteOne',model,_options_)
    response =await api
            .delete(req.params.model, _options_, req, config)
            .then((e) => e);
            saveLog({
              type:'api',
              action:'deleteOne',
              user:req.state?.user,
              model:req.params?.model,
              details:{
                input:_options_
              }
             })
   } 
  
   if(req.params.model.toLowerCase()=='medias'){ // Ok unlink
    unlinkMedia(mediasToRemove,config,req,_options_,api) // delete Media if model is medias
    }


       // Sync All thing
      updateModelViews(model,config)
   
      res.send(response);
    } catch (err) {
      //

        console.log(err)
      res.status(404).send({
        error:err?.message||err
      });
    }
  });
}




function startApp(config){
 try{
  config.server.listen(config.port,()=>{

    console.log(boxen(`
    ${'🚀 ADMIN PANEL'.gray.bold} 
    http://localhost:${config.port}                

    ${'📎 REST API '.gray.bold}
    http://localhost:${config.port}${config.routes.api}

    ${'📒 DOCUMENTATION '.gray.bold}
    https://cms.bitev.io

   ${'✨ VERSION'.gray.bold} ${config.package.version}    
    `,{
      textAlignment:'left',
      borderStyle:'round',
      borderColor:'gray'

     }))

    //consola.log("Socket IO 4.5.3 ⚡️".gray);
   // consola.log(`http://localhost:${config.port}/socket.io`);
   
   

  })
  if(!config.uuid && process.env.NODE_ENV !== 'production'){
    system(`open http://localhost:${config.port}`).catch()
  }
 } catch(err){
  console.log(err)
 }
}

module.exports = {
  loadRoute,
  loadExpress :loadRoute,
  startApp
};
