const fs = require("fs-extra");
const dayjs = require("dayjs");
const glob = require("glob");
// To debug it could be easier with source code:
// const openpgp = require("/media/phil/usbfarm/apxtrib/node_modules/openpgp/dist/node/openpgp.js");
const openpgp = require("openpgp");

/**
 * @api{get}/istauthenticated
 * @apiGroup Middlewares
 * @apiName isAUthenticated
 * @apiDescription Check that exist in town/tmp/tokens/xalias_xdays_xhash.substr(20,200) if not, check the xhash signature with message xalias_xdays come from public key belonging to xalias. If check pass then store a xhash into /tmp/tokens.
 * A process run each day to clean up all xhas tmp/tokens oldest than 24 hours. 
 * If authentify it returns header with xprofils store into a person objject -xtribes/person/alias
 *
 * @apiHeader {string} xalias anonymous or unique alias
 * @apiHeader {string} xapp  name of the webapp store in tribe/tribeid/www/xapp
 * @apiHeader {string} xlang the 2 letter request langage (if does not exist then return en = english).
 * @apiHeader {string} xtribe unique tribe name ere xapp exist
 * @apiHeader {string} xdays a timestamp 0 or generate during the authentifyme process
 * @apiHeader {string} xhash anonymous or signature of message: xalias_xdays created by alias private key during authentifyme process
 * @apiHeader {array[]} xprofils list of string profil apply into xtribe for xapp
 * @apiHeader {string} xuuid a unique number c reated the fisrt time a domain is visited
 * @apiHeader {integer} xtrkversion a version number link to tracking system
 *
 * @apiErrorExample {json} Error-Response:
 * 	HTTP/1/1 400 Not Found
 * 		{
 * 			status:400,
 * 			ref:"middlewares"
 * 			msg:"missingheaders",
 *      data: ["headermissing1"]
 *      }
 *@apiErrorExample {json} Error-Response:
 * 	HTTP/1/1 404 Not Found
 * 		{
 * 			status:404,
 * 			ref:"middlewares"
 * 			msg:"tribeiddoesnotexist",
 *      data: {xalias}
 *      }
 *
 * @apiHeaderExample {json} Header-Exemple:
 *     {
 * 		  xtribe:"apache",
 *          xalias:"toto",
 *          xhash:"",
 * 		  xdays:"123"
 *          xlang:"en",
 *          xapp:"popular"
 * 		}
 **/
const isAuthenticated = async (req, res, next) => {
  const withlog = true;
  const currentday = dayjs().date();
  fs.ensureDirSync(`${process.env.dirtown}/tmp/tokens`);
  let menagedone = fs.existsSync(
    `${process.env.dirtown}/tmp/tokens/menagedone${currentday}`
  );
  if (withlog)
    console.log(`menagedone${currentday} was it done today?:${menagedone}`);
  if (!menagedone) {
    // clean oldest
    const tsday = dayjs().valueOf(); // now in timestamp format
    glob.sync(`${process.env.dirtown}/tmp/tokens/menagedone*`).forEach((f) => {
      fs.removeSync(f);
    });
    glob.sync(`${process.env.dirtown}/tmp/tokens/*.json`).forEach((f) => {
      const fsplit = f.split("_");
      const elapse = tsday - parseInt(fsplit[2]);
      //24h 86400000 milliseconde 15mn 900000
      if (elapse && elapse > 86400000) {
        fs.remove(f);
      }
    });
    fs.outputFile(
      `${process.env.dirtown}/tmp/tokens/menagedone${currentday}`,
      "done by middleware/isAUthenticated"
    );
  }
  //Check register in tmp/tokens/
  if (withlog) console.log("isAuthenticate?", req.session.header, req.body);

  const resnotauth = {
    ref: "middlewares",
    msg: "notauthenticated",
    data: {
      xalias: req.session.header.xalias,
      xaliasexists: true,
    },
  };
  if (
    req.session.header.xalias == "anonymous" ||
    req.session.header.xhash == "anonymous"
  ) {
    if (withlog) console.log("alias anonymous means not auth");
    resnotauth.status = 401;
    return res.status(resnotauth.status).json(resnotauth);
  }

  let tmpfs = `${process.env.dirtown}/tmp/tokens/${req.session.header.xalias}_${req.session.header.xtribe}_${req.session.header.xdays}`;
  //max filename in ext4: 255 characters
  tmpfs += `_${req.session.header.xhash.substring(
    150,
    150 + tmpfs.length - 249
  )}.json`;

  const bruteforcepenalty = async (alias, action) => {
    const sleep = (ms) => {
      return new Promise((resolve) => setTimeout(resolve, ms));
    };
    const failstamp = `${process.env.dirtown}/tmp/tokens/${alias}.json`;
    if (action == "clean") {
      //to reinit bruteforce checker
      if (withlog) console.log("try to clean penalty file ", failstamp);
      fs.remove(failstamp, (err) => {
        if (err) console.log("Check forcebrut ", err);
      });
    } else if (action == "penalty") {
      const stamp = fs.existsSync(failstamp)
        ? fs.readJSONSync(failstamp)
        : { lastfail: dayjs().format(), numberfail: 0 };
      stamp.lastfail = dayjs().format();
      stamp.numberfail += 1;
      fs.outputJSON(failstamp, stamp);
      if (withlog) console.log("penalty:", stamp);
      await sleep(stamp.numberfail * 100); //increase of 0,1 second the answer time per fail
      if (withlog) console.log("time out penalty");
    }
  };
  if (!fs.existsSync(tmpfs)) {
    // need to check detached sign
    let publickey = "";
    console.log(process.cwd());
    console.log(process.env.PWD);
    console.log(__dirname);
    const aliasinfo = `${process.env.PWD}/nationchains/pagans/itm/${req.session.header.xalias}.json`;
    if (fs.existsSync(aliasinfo)) {
      publickey = fs.readJsonSync(aliasinfo).publickey;
    } else if (req.body.publickey) {
      resnotauth.data.xaliasexists = false;
      publickey = req.body.publickey;
    }
    if (publickey == "") {
      if (withlog) console.log("alias unknown");
      resnotauth.status = 404;
      resnotauth.data.xaliasexists = false;
      return res.status(resnotauth.status).send(resnotauth);
    }
    if (withlog) console.log("publickey", publickey);
    if (publickey.substring(0, 31) !== "-----BEGIN PGP PUBLIC KEY BLOCK") {
      if (withlog)
        console.log("Publickey is not valid as armored key:", publickey);
      await bruteforcepenalty(req.session.header.xalias, "penalty");
      resnotauth.status = 404;
      return res.status(resnotauth.status).send(resnotauth);
    }
    const clearmsg = Buffer.from(req.session.header.xhash, "base64").toString();
    if (clearmsg.substring(0, 10) !== "-----BEGIN") {
      if (withlog)
        console.log("xhash conv is not valid as armored key:", clearmsg);
      await bruteforcepenalty(req.session.header.xalias, "penalty");
      resnotauth.status = 404;
      return res.status(resnotauth.status).send(resnotauth);
    }
    if (withlog) console.log("clearmsg", clearmsg);
    const pubkey = await openpgp.readKey({ armoredKey: publickey });
    const signedMessage = await openpgp.readCleartextMessage({
      cleartextMessage: clearmsg,
    });
    const verificationResult = await openpgp.verify({
      message: signedMessage,
      verificationKeys: pubkey,
    });
    if (withlog) console.log(verificationResult);
    if (withlog) console.log(verificationResult.signatures[0].keyID.toHex());
    try {
      await verificationResult.signatures[0].verified;
      if (
        verificationResult.data !=
        `${req.session.header.xalias}_${req.session.header.xdays}`
      ) {
        resnotauth.msg = "signaturefailled";
        if (withlog)
          console.log(
            `message recu:${verificationResult.data} , message attendu:${req.session.header.xalias}_${req.session.header.xdays}`
          );
        await bruteforcepenalty(req.session.header.xalias, "penalty");
        resnotauth.status = 401;
        return res.status(resnotauth.status).send(resnotauth);
      }
    } catch (e) {
      resnotauth.msg = "signaturefailled";
      if (withlog) console.log("erreur", e);
      await bruteforcepenalty(req.session.header.xalias, "penalty");
      resnotauth.status = 401;
      return res.status(resnotauth.status).send(resnotauth);
    }
    // authenticated then get person profils (person = pagan for a xtrib)
    req.session.header.xprofils.push("pagans");
    const person = `${process.env.dirtown}/tribes/${req.session.header.xtribe}/persons/itm/${req.session.header.xalias}.json`;
    if (withlog) {
      console.log("Profils tribe/app management");
      console.log("person", person);
    }
    if (fs.existsSync(person)) {
      const infoperson = fs.readJSONSync(person);
      console.log(infoperson);
      infoperson.profils.forEach((p) => req.session.header.xprofils.push(p));
    }
    fs.outputJSONSync(tmpfs, req.session.header.xprofils);
  } else {
    //tmpfs exist get profils from identification process
    req.session.header.xprofils = fs.readJSONSync(tmpfs);
  }
  bruteforcepenalty(req.session.header.xalias, "clean");
  console.log(`${req.session.header.xalias} Authenticated`);
  next();
};
module.exports = isAuthenticated;
