/**
 *   АРХИВ ПУБЛИКАЦИЙ ОБУЧАЮЩИХСЯ И СОТРУДНИКОВ СУНЦ УрФУ
 *   Copyright © 2021, А.М.Гольдин. ISC license
 */
"use strict";

/* ПОДКЛЮЧЕНИЕ МОДУЛЕЙ И ОПРЕДЕЛЕНИЕ ГЛОБАЛЬНЫХ КОНСТАНТ
 * ----------------------------------------------------------------------- */
const http     = require("http"),
      nedb     = require("@yetzt/nedb"),
      fetch    = require("node-fetch"),
      api      = require("./api"),
      sertGen  = require("./api/sertGen"),
      putFile  = require("./api/putFile"),      
      {
         ADMIN, admEml, SALT, authServ, director, glred,
         smtpSrv, smtpPort, smtpUs, smtpPwd
      } = require("./config");

global.fs       = require("fs");
global.sendEml  = require("./api/sendEml");
global.ADMIN    = ADMIN;
global.ADMEML   = admEml;
global.DIRECTOR = director;
global.GLRED    = glred;
global.SMTPSRV  = smtpSrv;
global.SMTPPORT = smtpPort;
global.SMTPUS   = smtpUs;
global.SMTPPWD  = smtpPwd;

const mmmagic   = require("mmmagic"),
      magic     = mmmagic.Magic;
global.mType    = new magic(mmmagic.MAGIC_MIME_TYPE);

/* ИНИЦИАЛИЗАЦИЯ КОЛЛЕКЦИЙ БАЗЫ ДАННЫХ
 * ----------------------------------------------------------------------- */
global.db  = {};
db.articles = new nedb({filename:`${__dirname}/db/articles.db`, autoload:true});
db.staff    = new nedb({filename:`${__dirname}/db/staff.db`,    autoload:true});

/* ОПРЕДЕЛЕНИЯ ФУНКЦИЙ
 * ----------------------------------------------------------------------- */

// Промисификатор метода find() работы с базой db
// Пример вызова: let res = await dbFind("curric", {type: "class"}) 
global.dbFind = (collectionName, objFind) => {
   return new Promise((resolve, reject) => {
      db[collectionName].find(objFind, (err, docs) => {
         if (err) reject(err);
         else     resolve(docs);
      })
   })
};

// Изготавление хэша длины 24 из строки str
const hash = str => {   
   let
      alph = "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz",
      char, strNew, h = 0, pass = '';
   for (let j = 0; j < 24; j++) {
      strNew = j + str;
      for (let i = 0; i < strNew.length; i++) {
         char = strNew.charCodeAt(i);
         h = ((h << 5) - h) + char;
      }
      pass += alph[Math.abs(h) % alph.length];
   }
   return pass;
}

// Отправка ответа (otvet - объект ответа, content - тело)
const sendOtvet = (otvet, content) => {
   otvet.writeHead(200, {"Content-Type": "text/plain"});
   otvet.end(content);
}

/* СОБСТВЕННО ЦИКЛ ОБРАБОТКИ ЗАПРОСА
 * ----------------------------------------------------------------------- */
http.createServer(async (zapros, otvet) => {
   try {
      // Количество дней, прошедших с начала Юникс-эры
      let DT = ~~(Date.now()/(1000 * 3600 * 24));
      
      // Получаем параметры запроса
      let ADDR = zapros.socket.remoteAddress || "unknown";
      ADDR = ADDR.replace("::1", "127.0.0.1").replace(/\:.*\:/, '');

      let url     = new URL("http://" + zapros.headers.host + zapros.url);
      let qstring = (url.search || '').trim(); // вместе с ?;

      let cookies = {}, cookiesStr = zapros.headers.cookie || '';
      let cookiesArr = cookiesStr.split(';').map(x => {
         let keyValueArr = x.trim().split('=');
         cookies[keyValueArr[0]] = decodeURIComponent(keyValueArr[1]);
      });

      // Если пришел запрос на поиск (без авторизации)
      if (qstring == "?find") {
         let postData = '';
         zapros.on("data", dann => postData += dann.toString());
         zapros.on("end",  async () => {
            let cont = await api(postData, [0, 'a', 'a', 'a', '']);
            sendOtvet(otvet, cont);               
         });           
      }

      // Первичная авторизация (логин-пароль передается в query string)
      else if (qstring.includes('-')) {         

         // От сервера авторизации приходит либо none, либо
         // "pupkin,Пупкин,Василий,Петрович,"
         let resp = "none";
         try {
            resp = await (await fetch(`${authServ + qstring}`)).text();
         } catch (e) {;}
         if (resp == "none") sendOtvet(otvet, "noauth");
         else {
            // Определяем роли этого клиента (Editor, Corrector, Reviewer)
            // присобачиваем в качестве первой цифры куки (двоичная маска)
            let login = resp.split(',')[0], roles = 0;
            if (login == ADMIN) roles = 8;
            else {
               let res = await dbFind("staff", {login: login});
               if (res.length) roles = res[0].roles;
            }
            // Готовим куку u=4pupkin,Пупкин,Василий,Петрович,jgkjhs6
            let token = hash(SALT + ADDR + DT + roles + resp),
            cook = `u=${encodeURIComponent(roles+resp+token)}; path=/`;

            otvet.writeHead(200, {
               "Content-Type": "text/plain", "Set-Cookie": cook});
            otvet.end("auth");
         }
      }

      // Обработка запроса к API (кроме первичной авторизации)
      else {
         // Объект с данными юзера (роли, логин, фам, имя, отч (возм., пустое))
         let user = [];

         // Проверяем, авторизован ли клиент
         let auth = false;         
         if (cookies['u']) {
            let [lgRl, uFam, uName, uOtch, uToken] = cookies['u'].split(','),
               tokTrue = hash(SALT + ADDR + DT
                  + [lgRl, uFam, uName, uOtch].join(',') + ',');
            if (uToken == tokTrue) {
               auth = true;
               let uRoles = Number(lgRl[0]), uLgn = lgRl.slice(1);
               user = [uRoles, uLgn, uFam, uName, uOtch];
            }
         }

         if (!auth) sendOtvet(otvet, "forbidden");

         // Если пришел запрос на добавление файла <host>/api/?put_<id>
         else if (/^\?put_/.test(qstring)) {
            let id = qstring.replace("?put_", ''),
                fl = Buffer.alloc(0);
            zapros.on("data", dann => fl = Buffer.concat([fl, dann]));
            zapros.on("end",  async () => {
               let cont = await putFile(user, id, fl);
               sendOtvet(otvet, cont);               
            });
         }

         // Если пришел запрос изображения <host>/api/?draft_<id>/<flName>
         else if (/^\?draft_\w+\/\d+\.(jpg|png|svg)$/.test(qstring)) {
            const TYPES = {
               "jpg":"image/jpeg", "png":"image/png", "svg":"image/svg+xml"
            };
            let fullName = qstring.replace("?draft_", ''),
                     ext = fullName.split('.')[1];                    ;
            fs.readFile(__dirname + `/draft/${fullName}`, (e, data) => {
               if(e) {
                  otvet.writeHead(404, {"Content-Type": "text/plain"});
                  otvet.end("404 Not found");
               }
               else {
                  otvet.writeHead(200, {"Content-Type": TYPES[ext]});
                  otvet.end(data);
               }
            });               
         }

         // Если пришел запрос просмотра черновика <host>/api/?view_<id>
         else if (/^\?view_\w+$/.test(qstring)) {
            let id = qstring.replace("?view_", '');                    ;
            let cont = await api(
               JSON.stringify({f:"loadArticle", dt:id}),
               user
            );
            otvet.writeHead(200, {"Content-Type": "text/html"});
            otvet.end(cont);       
         }

         // Если пришел запрос сертификата статьи вида <host>/api/?fjhgdj
         else if (/^\?\w{3,20}$/.test(qstring))
            sertGen(otvet, qstring.slice(1), user);         

         // Если пришел запрос к api
         else {
            let postData = '';
            zapros.on("data", dann => postData += dann.toString());
            zapros.on("end",  async () => {
               let cont = await api(postData, user);
               sendOtvet(otvet, cont);               
            });           
         }
      }
   }
   catch(e) {
      otvet.writeHead(500, {"Content-Type": "text/plain"});
      otvet.end("Internal Server Error");
   }
   
}).listen(8080);

let now = (new Date()).toString().replace(/ \(.*\)/, '');
console.info(`${now} lycArch api server стартовал на порту 8080`);
