var express = require('express');
var router = express.Router();
var { KB, Namespace } = require('../models/kb_setting');
var winston = require('../config/winston');
const JobManager = require('../utils/jobs-worker-queue-manager/JobManagerV2');
const { AiReindexService } = require('../services/aiReindexService');
const { Webhook } = require('../models/webhook');
const webhookService = require('../services/webhookService');
const errorCodes = require('../errorCodes');
const aiManager = require('../services/aiManager');
var ObjectId = require('mongoose').Types.ObjectId;
const default_embedding = require('../config/kb/embedding');

const port = process.env.PORT || '3000';
let TILEBOT_ENDPOINT = "http://localhost:" + port + "/modules/tilebot/";
if (process.env.TILEBOT_ENDPOINT) {
    TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + "/"
}
winston.debug("TILEBOT_ENDPOINT: " + TILEBOT_ENDPOINT);

const KB_WEBHOOK_TOKEN = process.env.KB_WEBHOOK_TOKEN || 'kbcustomtoken';
const AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL;
const JOB_TOPIC_EXCHANGE = process.env.JOB_TOPIC_EXCHANGE_TRAIN || 'tiledesk-trainer';

let jobManager = new JobManager(AMQP_MANAGER_URL, {
  debug: false,
  topic: JOB_TOPIC_EXCHANGE,
  exchange: JOB_TOPIC_EXCHANGE
})

jobManager.connectAndStartPublisher((status, error) => {
  if (error) {
    winston.error("connectAndStartPublisher error: ", error);
  } else {
    winston.info("KbRoute - ConnectPublisher done with status: ", status);
  }
})

let default_engine = {
  name: "pinecone",
  type: process.env.PINECONE_TYPE || "pod",
  apikey: "",
  vector_size: 1536,
  index_name: process.env.PINECONE_INDEX
}

let default_engine_hybrid = {
  name: "pinecone",
  type: process.env.PINECONE_TYPE_HYBRID || "serverless",
  apikey: "",
  vector_size: 1536,
  index_name: process.env.PINECONE_INDEX_HYBRID
}

router.post('/kb/reindex', async (req, res) => {

  winston.verbose("/kb/reindex webhook called")
  winston.debug("(webhook) req.body: ", req.body);

  if (!req.headers['x-auth-token']) {
    winston.error("(webhook) Unauthorized: A x-auth-token must be provided")
    return res.status(401).send({ success: false, error: "Unauthorized", message: "A x-auth-token must be provided" })
  }

  if (req.headers['x-auth-token'] != KB_WEBHOOK_TOKEN) {
    winston.error("(webhook) Unauthorized: You don't have the authorization to accomplish this operation")
    return res.status(401).send({ success: false, error: "Unauthorized", message: "You don't have the authorization to accomplish this operation" });
  }
  
  let content_id = req.body.content_id;

  let kb;
  try {
    kb = await KB.findById(content_id);
  } catch (err) {
    winston.error("(webhook) Error getting kb content: ", err);
    return res.status(500).send({ success: false, error: "Error getting content with id " + content_id });
  }

  if (!kb) {
    winston.warn("(webhook) Kb content not found with id " + content_id + ". Deleting scheduler...");

    // Assuming the content has been deleted. The scheduler should be stopped and deleted.
    res.status(200).send({ success: true, message: "Content no longer exists. Deleting scheduler..." })

    setTimeout( async () => {
      let aiReindexService = new AiReindexService();
      let deleteResponse = await aiReindexService.delete(content_id).catch((err) => {
        winston.error("(webhook) Error deleting scheduler ", err);
        winston.error("(webhook) Error deleting scheduler " + err);
        return;
      });
      winston.verbose("(webhook) delete response: ", deleteResponse);
      return;
    }, 10000);
    return;
  }

  const namespace_id = kb.namespace;
  let namespace;
  try {
    namespace = await aiManager.checkNamespace(kb.id_project, namespace_id);
  } catch (err) {
    let errorCode = err?.errorCode ?? 500;
    return res.status(errorCode).send({ success: false, error: err.error });
  }

  if (kb.type === 'sitemap') {

    const urls = await aiManager.fetchSitemap(kb.source).catch((err) => {
      winston.error("(webhook) Error fetching sitemap: ", err);
      return res.status(500).send({ success: false, error: err });
    })

    if (urls.length === 0) {
      return res.status(400).send({ success: false, error: "No url found on sitemap" });
    }

    let existingKbs;
    try {
      existingKbs = await KB.find({ id_project: kb.id_project, namespace: namespace_id, sitemap_origin_id: content_id}).lean().exec();
    } catch(err) {
      winston.error("(webhook) Error finding existing contents: ", err);
      return res.status(500).send({ success: false, error: "Error finding existing sitemap contents" });
    }

    const result = await aiManager.foundSitemapChanges(existingKbs, urls).catch((err) => {
      winston.error("(webhook) error finding sitemap differecens ", err);
      return res.status(400).send({ success: false, error: "Error finding sitemap differecens" });
    })

    if (!result) return; // esco qui

    const { addedUrls, removedIds } = result;

    if (removedIds.length > 0) {
      const idsSet = new Set(removedIds);
      const kbsToDelete = existingKbs.filter(obj => idsSet.has(obj._id));

      aiManager.removeMultipleContents(namespace, kbsToDelete).catch((err) => {
        winston.error("(webhook) error deleting multiple contents: ", err);
      })
    }

    if (addedUrls.length > 0) {
      const options = {
        sitemap_origin_id: kb.id,
        sitemap_origin: kb.source,
        scrape_type: kb.scrape_type,
        scrape_options: kb.scrape_options,
        refresh_rate: kb.refresh_rate,
        ...(kb.tags ? { tags: kb.tags } : {}),
        ...(kb.situated_context === true && (kb.scrape_type === 0 || kb.scrape_type === '0')
          ? { situated_context: true }
          : {}),
      }
      aiManager.addMultipleUrls(namespace, addedUrls, options).catch((err) => {
        winston.error("(webhook) error adding multiple urls contents: ", err);
      })
    }

    res.status(200).send({ success: true, message: "Content queued for reindexing" });

  } else {

    let json = {
      id: kb._id,
      type: kb.type,
      source: kb.source,
      content: "",
      namespace: kb.namespace,
      refresh_rate: kb.refresh_rate,
      last_refresh: kb.last_refresh
    }
  
    if (kb.scrape_type) {
      json.scrape_type = kb.scrape_type
    }
  
    if (kb.scrape_options) {
      json.parameters_scrape_type_4 = {
        tags_to_extract: kb.scrape_options.tags_to_extract,
        unwanted_tags: kb.scrape_options.unwanted_tags,
        unwanted_classnames: kb.scrape_options.unwanted_classnames
      }
    }
  
    let namespace = await Namespace.findOne({ id: kb.namespace }).catch((err) => {
      winston.error("(webhook) Error getting namespace ", err)
      return res.status(500).send({ success: false, error: err })
    })
  
    if (!namespace) {
      winston.warn("(webhook) Namespace not found with id " + kb.namespace);
      return res.status(500).send({ success: false, error: err })
    }
  
    json.engine = namespace.engine || (namespace.hybrid ? default_engine_hybrid : default_engine);
    json.hybrid = namespace.hybrid;
    
    let embedding = namespace.embedding || default_embedding;
    embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;
    json.embedding = embedding;

    const situated_context_obj = aiManager.normalizeSituatedContext(kb.situated_context);
    if (situated_context_obj) {
      json.situated_context = situated_context_obj;
    }

    let resources = [];
    resources.push(json);
  
    if (process.env.NODE_ENV !== 'test') {
      aiManager.scheduleScrape(resources, namespace.hybrid);
    }
  
    res.status(200).send({ success: true, message: "Content queued for reindexing" });

  }
})


router.post('/kb/status', async (req, res) => {

  winston.info("(webhook) kb status called");
  winston.info("(webhook) req.body: ", req.body);

  winston.info("(webhook) x-auth-token: " + req.headers['x-auth-token']);
  winston.info("(webhook) KB_WEBHOOK_TOKEN: " + KB_WEBHOOK_TOKEN);

  if (!req.headers['x-auth-token']) {
    winston.error("Unauthorized: A x-auth-token must be provided")
    return res.status(401).send({ success: false, error: "Unauthorized", message: "A x-auth-token must be provided" })
  }

  if (req.headers['x-auth-token'] != KB_WEBHOOK_TOKEN) {
    winston.error("Unauthorized: You don't have the authorization to accomplish this operation")
    return res.status(401).send({ success: false, error: "Unauthorized", message: "You don't have the authorization to accomplish this operation" });
  }

  let body = req.body;
  winston.verbose('/webhook kb status body: ', body);

  let kb_id = body.id;
  winston.verbose('/webhook kb status body: ' + kb_id);

  let update = {};

  if (body.status) {
    update.status = body.status;
  }

  KB.findByIdAndUpdate(kb_id, update, { new: true }, (err, kb) => {
    if (err) {
      winston.error(err);
      return res.status(500).send({ success: false, error: err });
    }

    if (!kb) {
      winston.info("Knwoledge Base content to be updated not found")
      return res.status(404).send({ success: false, messages: "Knwoledge Base content not found with id " + kb_id });
    }

    winston.info("kb updated succesfully: ", kb);
    res.status(200).send(kb);
  })

})

router.all('/:webhook_id', async (req, res) => {

  let webhook_id = req.params.webhook_id;
  let payload = req.body;
  payload.webhook_http_method = req.method;
  let params = req.query;
  let dev = params.dev;
  delete params.dev;
  if (params) {
    payload.webhook_query_params = params;
  }

  let webhook = await Webhook.findOne({ webhook_id: webhook_id }).catch((err) => {
    winston.error("Error finding webhook: ", err);
    return res.status(500).send({ success: false, error: err });
  })

  if (!webhook) {
    winston.warn("Webhook not found with id " + webhook_id);
    return res.status(404).send({ success: false, error: "Webhook not found with id " + webhook_id });
  }

  if (!webhook.enabled) {
    winston.verbose("Webhook " + webhook_id + " is currently turned off")
    return res.status(422).send({ success: false, error: "Webhook " + webhook_id + " is currently turned off"})
  }

  const rate_manager = req.app.get("rate_manager");
  const allowed = await rate_manager.canExecute(webhook.id_project, null, 'webhook');
  if (!allowed) {
    winston.warn("Webhook rate limit exceeded for project " + webhook.id_project)
    return res.status(429).send({ message: "Rate limit exceeded"});
  }

  payload.request_id = "automation-request-" + webhook.id_project + "-" + new ObjectId() + "-" + webhook_id;

  // To delete - Start
  // This endpoint will be used only for production webhooks, so is no longer
  // necessary to pass dev and redis_client to webhookService.run().
  let redis_client = req.app.get('redis_client');
  // and substitute currect run with the following one
  //webhookService.run(webhook, payload)
  // To delete - End
  webhookService.run(webhook, payload, dev, redis_client).then((response) => {
    return res.status(200).send(response);
  }).catch((err) => {
    if (err.code === errorCodes.WEBHOOK.ERRORS.NO_PRELOADED_DEV_REQUEST) {
      return res.status(422).send({ success: false, message: "Development webhook is currently turned off", code: err.code })
    } else {
      let status = err.status || 500;
      return res.status(status).send(err.data);
    }
  })
  
})

router.all('/:webhook_id/dev', async (req, res) => {

  let webhook_id = req.params.webhook_id;
  let payload = req.body;
  payload.webhook_http_method = req.method;
  let params = req.query;
  delete params.dev;
  if (params) {
    payload.webhook_query_params = params;
  }

  let webhook = await Webhook.findOne({ webhook_id: webhook_id }).catch((err) => {
    winston.error("Error finding webhook: ", err);
    return res.status(500).send({ success: false, error: err });
  })

  if (!webhook) {
    winston.warn("Webhook not found with id " + webhook_id);
    return res.status(404).send({ success: false, error: "Webhook not found with id " + webhook_id });
  }

  let redis_client = req.app.get('redis_client');
  webhookService.run(webhook, payload, true, redis_client).then((response) => {
    return res.status(200).send(response);
  }).catch((err) => {
    if (err.code === errorCodes.WEBHOOK.ERRORS.NO_PRELOADED_DEV_REQUEST) {
      return res.status(422).send({ success: false, message: "Development webhook is currently turned off", code: err.code })
    } else {
      let status = err.status || 500;
      return res.status(status).send(err.data);
    }
  })
  
})


// async function generateChatbotToken(chatbot) {
//   let signOptions = {
//     issuer: 'https://tiledesk.com',
//     subject: 'bot',
//     audience: 'https://tiledesk.com/bots/' + chatbot._id,
//     jwtid: uuidv4()
//   };

//   let botPayload = chatbot.toObject();
//   let botSecret = botPayload.secret;

//   var bot_token = jwt.sign(botPayload, botSecret, signOptions);
//   return bot_token;
// }

module.exports = router;
