const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const slugify = require('slugify');
const { Op, QueryTypes } = require("sequelize");// pour certaines requêtes sql

const config = require("../config/main.js");
const configUsers = require("../config/users.js");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");

const tool = require("../tools/main");
const toolError = require("../tools/error");
const toolFile = require("../tools/file");
const toolMail = require("../tools/mail");

const txt = require("../lang/"+config.adminLang+"/user");
const txtGeneral = require("../lang/"+config.adminLang+"/general");

const answerCtrl = require("./answer");
const subscriptionCtrl = require("./subscription");

// Retourne certaines configurations utiles aux formulaires où vues
exports.checkEmailIsFree = async (req, res, next) =>
{
    try
    {
        const user=await searchUserByEmail(req.body.emailTest);
        const response={ };
        if(user)
        {
            response.free=false;
            if(user.Subscription)
                response.validated=true;
            else
                response.validated=false;
        }
        else
            response.free=true;
        res.status(200).json(response);
        next();
    }
    catch(e)
    {
        next(e);
    }
}

exports.getGodfatherId = async (req, res, next) =>
{
    try
    {
        const godfather=await searchIdGodfather(req.body.codeTest);
        if(godfather)
            res.status(200).json(godfather.id);
        else
            res.status(204).json({ errors: txt.godfatherNotFound });
        next();
    }
    catch(e)
    {
        next(e);
    }
}

exports.signup = async (req, res, next) =>
{
    try
    {        
        const db = require("../models/index");
        if(req.body.cguOk !== "true")
            res.status(400).json({ errors: [txt.needUGCOk] });
        else if(req.body.password.length < config.passwordMinLength) // si le champ est supprimé du formulaire d'inscription, générer un mot de passe ok ici
            res.status(400).json({ errors: [txt.needLongPassWord.replace("MIN_LENGTH", config.passwordMinLength)] });
        else
        {
            req.body.GodfatherId=null;
            if(req.body.codeGodfather!=="")
            {
                const godfather=await searchIdGodfather(req.body.codeGodfather);
                if(godfather)
                    req.body.GodfatherId=godfather.id;
            }
            req.body.password=await bcrypt.hash(req.body.password, config.bcryptSaltRounds);
            const user=await db["User"].create({ ...req.body }, { fields: ["name", "email", "password", "newsletterOk", "GodfatherId", "timeDifference"] });
            req.body.UserId=user.id;
            // si l'utilisateur a répondu à un quiz avant de créer son compte, on enregistre son résultats.
            if(req.body.QuestionnaireId)
            {
                await Promise.all([
                    db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] }),
                    db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] })
                    ]); // pas nécessaire ici de créer le cache JSON, car il sera créé lors de la première connexion au compte.
            }
            await sendValidationLink(user);
            res.status(201).json({ message: txt.mailValidationMessage });
            next();
        }
    }
    catch(e)
    {
        const returnAPI=toolError.returnSequelize(e);
        if(returnAPI.messages)
        {
            res.status(returnAPI.status).json({ errors : returnAPI.messages });
            next();
        }
        else
            next(e);
    }
}

exports.signupValidation = async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const datas=await checkTokenUser(req.params.token);
        if(datas)
        {
            if(datas.Subscription)
                res.status(200).json({ errors: [txt.validationAlreadyMessage] });
            else
            {
                const now=new Date();
                await Promise.all([
                    db["Subscription"].create({ numberOfDays: config.freeAccountTimingInDays, numberOfDays: config.defaultReceiptDays, noticeOk: datas.User.newsletterOk, UserId: datas.User.id }),
                    db["User"].update({ connectedAt: now }, { where: { id : datas.User.id }, limit:1 })
                    ]);
                creaUserJson(datas.User.id);
                const mapMail =
                {
                   USER_NAME: datas.User.name,
                   NOM_SITE : config.siteName,
                   EMAIL : config.senderEmail,
                   LINK_URL : config.siteUrl+"/"+configTpl.connectionPage
                };
                const mailDatas =
                {
                    mailSubject: txt.mailWelcomeSubject,
                    mailPreheader: txt.mailWelcomeSubject,
                    mailTitle: txt.mailWelcomeSubject,
                    mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.connectionPage,
                    mailHeaderLinkTxt: txt.mailWelcomeLinkTxt,
                    mailMainContent: tool.replaceAll(txt.mailWelcomeBodyHTML, mapMail),
                    linksCTA: [{ url:config.siteUrl+"/"+configTpl.connectionPage, txt:txt.mailWelcomeLinkTxt }],
                    mailRecipientAddress: datas.User.email
                }
                toolMail.sendMail(0, datas.User.email, txt.mailWelcomeSubject, tool.replaceAll(txt.mailWelcomeBodyTxt, mapMail), "", mailDatas);
                if(datas.User.GodfatherId!==null)
                {
                    const godfather=await searchUserById(datas.User.GodfatherId);
                    if(godfather)
                    {
                        mapMail.USER_NAME=godfather.User.name;
                        mapMail.EMAIL=datas.User.email;
                        const mailDatas2=
                        {
                            mailSubject: txt.mailThankGodfatherSubject,
                            mailPreheader: txt.mailThankGodfatherSubject,
                            mailTitle: txt.mailThankGodfatherSubject,
                            mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.connectionPage,
                            mailHeaderLinkTxt: txt.mailThankGodfatherLinkTxt,
                            mailMainContent: tool.replaceAll(txt.mailThankGodfatherBodyHTML, mapMail),
                            mailRecipientAddress: godfather.User.email
                        }
                        toolMail.sendMail(godfather.User.smtp, godfather.User.email, txt.mailThankGodfatherSubject, tool.replaceAll(txt.mailThankGodfatherBodyTxt, mapMail), "", mailDatas2);
                    }
                }
                res.status(200).json(
                {
                    userId: datas.User.id,
                    token: jwt.sign({ userId: datas.User.id }, config.tokenPrivateKey, { expiresIn: config.tokenConnexionMinTimeInHours })
                });
            }
        }
        else
            res.status(401).json({ errors: txt.badLinkValidationMessage });
        next();
    }
    catch(e)
    {
        next(e);
    }
}

exports.checkToken = async (req, res, next) =>
{
    try
    {
        const datas=await checkTokenUser(req.params.token);
        if(datas && datas.Subscription)
        {
            const beginSubTS=new Date(datas.Subscription.createdAt).getTime();
            const nbDaysOk=datas.Subscription.numberOfDays-Math.round((Date.now()-beginSubTS)/1000/3600/24);
            res.status(200).json(
            {
                isValid: true,
                id: datas.User.id,
                name: datas.User.name,
                language: datas.User.language,
                timeDifference : datas.User.timeDifference,
                status: datas.User.status,
                nbDaysOk : nbDaysOk
            });
        }
        else
            res.status(200).json({ isValid: false });
        next();
    }
    catch(e)
    {
        next(e);
    }
}

// Reçoit les données du formulaire de connexion avec mot de passe.
exports.login = async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        // Est-ce qu'un compte existe pour l'adresse e-mail envoyée ?
        const emailSend=tool.trimIfNotNull(req.body.email);
        const user=await db["User"].findOne({ attributes: ["id", "password", "email", "name", "status"], where: { email: emailSend } });
        if(!user)
            res.status(404).json({ errors: [txt.emailNotFound] });
        else
        {
            // Est-ce ce compte a déjà été validé par l'utilisateur ? Si non, on lui envoie un nouveau lien de validation.
            const subscription=await db["Subscription"].findOne({ attributes: ["id"], where: { UserId: user.id } });
            if(!subscription)
            {
                await sendValidationLink(user);
                res.status(400).json({ errors: [txt.needValidationToLogin] });
            }
            else
            {
                const nowTS=new Date().getTime();
                // L'utilisateur n'a-t-il pas testé de se connecter de trop nombreuses fois sans succès ?
                const countLogin=await toolFile.readJSON(config.dirTmpLogin, slugify(emailSend));
                if(countLogin && countLogin.nb >= config.maxLoginFail && countLogin.lastTime > (nowTS-config.loginFailTimeInMinutes*60*1000))
                    res.status(401).json({ errors: [txt.tooManyLoginFails.replace("MINUTES", config.loginFailTimeInMinutes)] });
                else
                {
                    // Le mot du passe envoyé est-il cohérent avec celui de la base de données après chiffrement ?
                    const valid = await bcrypt.compare(req.body.password, user.password);
                    if (!valid)
                    {
                        res.status(401).json({ errors: [txt.badPassword] });
                        // On comptabilise l'erreur :
                        let newCountLogin={ nb:1, lastTime:nowTS };
                        if(countLogin.nb  && countLogin.lastTime > (nowTS-config.loginFailTimeInMinutes*60*1000))
                            newCountLogin.nb=countLogin.nb+1;
                        await toolFile.createJSON(config.dirTmpLogin, slugify(emailSend), newCountLogin);
                    }
                    else
                    {
                        // Si tout est ok, on enregistre la date de connexion + retourne un token de connexion.
                        const now=new Date();
                        const timeDifference=req.body.timeDifference;
                        db["User"].update({ connectedAt: now, timeDifference: timeDifference }, { where: { id : user.id }, limit:1 });
                        creaUserJson(user.id);
                        // Connexion à rallonge uniquement possible pour utilisateur de base :
                        let loginTime=config.tokenConnexionMinTimeInHours;
                        if((req.body.keepConnected==="true") && (user.status==="user"))
                            loginTime=config.tokenConnexionMaxTimeInDays;
                        // Si des données concernant un quiz ont été transmises, on les enregistre ici :
                        req.body.UserId=user.id;
                        if(req.body.QuestionnaireId)
                        {
                            const access=await subscriptionCtrl.checkQuestionnaireAccess(user.id, req.body.QuestionnaireId);
                            if(!access)
                                await db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] });
                            await db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] });
                            answerCtrl.creaUserAnswersJson(req.body.UserId);
                            answerCtrl.creaUserQuestionnairesWithoutAnswerJson(req.body.UserId);
                            answerCtrl.creaUserStatsAnwsersJson(req.body.UserId);
                        }
                        res.status(200).json(
                        {
                            userId: user.id,
                            status: user.status,
                            connexionTime: loginTime,
                            token: jwt.sign({ userId: user.id }, config.tokenPrivateKey, { expiresIn: loginTime })
                        });
                    }
                }
            }
        }
        next();
    }
    catch(e)
    { 
        next(e);
    }
}

// Reçoit les données du formulaire de connexion avec demande de recevoir un lien de connexion.
exports.getLoginLink = async (req, res, next) =>
{
    try
    {
        // Est-ce qu'un compte existe pour l'adresse e-mail envoyée ?
        const emailSend=tool.trimIfNotNull(req.body.email);
        const userDatas=await searchUserByEmail(emailSend);
        if(!userDatas)
            res.status(404).json({ errors: [txt.emailNotFound] });
        else if(userDatas.User.status!=="user") // seuls les utilisateurs de base peuvent se connecter de cette façon.
            res.status(403).json({ errors: [txtGeneral.notAllowed] });
        else
        {
            // Est-ce ce compte a déjà été validé par l'utilisateur ? Si non, on lui envoie un nouveau lien de validation.
            if(!userDatas.Subscription)
            {
                await sendValidationLink(userDatas.User);
                res.status(401).json({ errors: [txt.needValidationToLogin] });
            }
            else
            {
                const token=jwt.sign({ userId:userDatas.User.id, keepConnected:req.body.keepConnected }, config.tokenPrivateKey, { expiresIn: config.tokenLoginLinkTimeInHours });
                const mapMail =
                {
                   USER_NAME: userDatas.User.name,
                   LINK_URL : config.siteUrl+"/"+configTpl.loginLinkPage+token
                };
                const mailDatas=
                {
                    mailSubject: txt.mailLoginLinkSubject,
                    mailPreheader: txt.mailLoginLinkSubject,
                    mailTitle: txt.mailLoginLinkSubject,
                    mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.loginLinkPage+token,
                    mailHeaderLinkTxt: txt.mailLoginLinkTxt,
                    mailMainContent: tool.replaceAll(txt.mailLoginLinkBodyHTML, mapMail),
                    linksCTA: [{ url:config.siteUrl+"/"+configTpl.loginLinkPage+token, txt:txt.mailLoginLinkTxt }],
                    mailRecipientAddress: userDatas.User.email
                }
                await toolMail.sendMail(userDatas.User.smtp, userDatas.User.email, txt.mailLoginLinkSubject, tool.replaceAll(txt.mailLoginLinkBodyTxt, mapMail), "", mailDatas);
                res.status(200).json({ message: txt.mailLoginLinkMessage.replace("*TIMING*", config.tokenLoginLinkTimeInHours) });
            }
        }
        next();
    }
    catch(e)
    { 
        next(e);
    }
}

exports.checkLoginLink = async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const userDatas= await checkTokenUser(req.body.t);
        if(userDatas.User.status!=="user")
            res.status(403).json({ errors: [txtGeneral.notAllowed] });
        else if(userDatas.Subscription)
        {
            const now=new Date();
            const timeDifference=req.body.timeDifference;
            db["User"].update({ connectedAt: now, timeDifference: timeDifference }, { where: { id : userDatas.User.id }, limit:1 });
            creaUserJson(userDatas.User.id);
            let loginTime=config.tokenConnexionMinTimeInHours;
            if(userDatas.decodedToken.keepConnected===true)
                loginTime=config.tokenConnexionMaxTimeInDays;
            // si des données concernant un quiz ont été transmises, je les enregistre ici :
            if(req.body.QuestionnaireId)
            {
                req.body.UserId=userDatas.User.id;
                const access=await subscriptionCtrl.checkQuestionnaireAccess(userDatas.User.id, req.body.QuestionnaireId);
                if(!access)
                    await db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] });
                await db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] });
                answerCtrl.creaUserAnswersJson(req.body.UserId);
                answerCtrl.creaUserStatsAnwsersJson(req.body.UserId);
                answerCtrl.creaUserQuestionnairesWithoutAnswerJson(req.body.UserId);              
            }
            res.status(200).json(
            {
                userId: userDatas.User.id,
                connexionTime: loginTime,
                token: jwt.sign({ userId: userDatas.User.id }, config.tokenPrivateKey, { expiresIn: loginTime })
            });
        }
        else // ne devrait pas être possible...
        {
            await sendValidationLink(userDatas.User);
            res.status(401).json({ errors: [txt.needValidationToLogin] });  
        }
        next();
    }
    catch(e)
    { 
        next(e);
    }
}

exports.modify = async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        if(req.body.newPassword && req.body.newPassword.length < config.passwordMinLength)
            res.status(400).json({ errors: txt.needLongPassWord.replace("MIN_LENGTH", config.passwordMinLength) });
        else
        {           
            if(req.connectedUser.User.status==="user")
            {
                await Promise.all([
                        db["User"].update({ ...req.body }, { where: { id : req.connectedUser.User.id } , fields: ["name", "language", "newsletterOk", "timeDifference"], limit:1 }),
                        db["Subscription"].update({ ...req.body }, { where: { UserId : req.connectedUser.User.id }, fields: ["receiptDays", "noticeOk"], limit:1 })
                        ]);
                creaUserJson(req.connectedUser.User.id);
                const messageRetour=[txt.updatedOkMessage];
                if(req.body.newPassword || (req.body.email != req.connectedUser.User.email))
                {
                    const newLogin={ userId: req.connectedUser.User.id };
                    req.body.email=tool.trimIfNotNull(req.body.email);
                    if(req.body.email != req.connectedUser.User.email)
                    {
                        if (! /[A-Za-z0-9_.-]+@[A-Za-z0-9_.-]+\.[A-Za-z]{2,6}/.test(req.body.email))
                            messageRetour.push(txt.updatedNeedGoodEmail);
                        else
                        {
                            const emailFree=await searchUserByEmail(req.body.email);
                            if(emailFree!==false)
                                messageRetour.push(txt.updatedNeedUniqueEmail.replace("NEW_EMAIL", req.body.email));
                            else
                                newLogin.email=req.body.email;
                        }
                    }
                    if(req.body.newPassword)
                        newLogin.password=await bcrypt.hash(req.body.newPassword, config.bcryptSaltRounds);
                    if(newLogin.email || newLogin.password)
                    {
                        const token=jwt.sign(newLogin, config.tokenPrivateKey, { expiresIn: config.tokenLoginChangingTimeInHours });
                        const mapMail =
                        {
                           USER_NAME: req.body.name,
                           LINK_URL : config.siteUrl+"/"+configTpl.newLoginLinkPage+token
                        };
                        const mailDatas=
                        {
                            mailSubject: txt.mailUpdateLoginSubject,
                            mailPreheader: txt.mailUpdateLoginSubject,
                            mailTitle: txt.mailUpdateLoginSubject,
                            mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.newLoginLinkPage+token,
                            mailHeaderLinkTxt: txt.mailUpdateLoginLinkTxt,
                            mailMainContent: tool.replaceAll(txt.mailUpdateLoginBodyHTML, mapMail),
                            linksCTA: [{ url:config.siteUrl+"/"+configTpl.newLoginLinkPage+token, txt:txt.mailUpdateLoginLinkTxt }],
                            mailRecipientAddress: req.body.email
                        }
                        await toolMail.sendMail(req.connectedUser.User.smtp, req.body.email, txt.mailUpdateLoginSubject, tool.replaceAll(txt.mailUpdateLoginBodyTxt, mapMail), "", mailDatas);
                        messageRetour.push(txt.mailUpdateLoginLinkMessage.replace("NEW_EMAIL", req.body.email));
                    }
                }
                res.status(200).json({ message: messageRetour });
            }
            else
            {
                let userToUpdating=await searchUserById(req.params.id);
                if(!userToUpdating || !userToUpdating.Subscription)
                    res.status(400).json({ errors: [txt.updatedNeedValidatedUser] });
                else
                {
                    const messageRetour=[txtGeneral.updateOkMessage];
                    if(req.body.newPassword)
                        req.body.password=await bcrypt.hash(req.body.newPassword, config.bcryptSaltRounds);
                    if(req.body.newGodfatherId!=undefined)
                    {
                        const godFather=searchIdGodfather(req.body.newGodfatherId);
                        if(empty(godFather))
                            messageRetour.push(txt.updatedNeedGoodGodfather);
                        else
                            req.body.GodfatherId=godFather.id;
                    }
                    switch(req.connectedUser.User.status)
                    {
                        case "admin":
                            await Promise.all([
                                db["User"].update({ ...req.body }, { where: { id : req.params.id }, limit:1 }),
                                db["Subscription"].update({ ...req.body }, { where: { UserId : req.params.id }, limit:1 })
                                ]);
                            break;
                        case "manager":
                            await Promise.all([
                                db["User"].update({ ...req.body }, { where: { id : req.params.id }, fields: ["name", "email", "password", "language", "adminComments", "smtp", "newsletterOk", "GodfatherId"], limit:1 }),
                                db["Subscription"].update({ ...req.body }, { where: { UserId : req.params.id }, fields: ["numberOfDays", "receiptDays", "noticeOk"], limit:1 })
                                ]);
                            break;
                        case "creator":
                            await Promise.all([
                                db["User"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["name", "email", "password", "language", "adminComments"], limit:1 }),
                                db["Subscription"].update({ ...req.body }, { where: { UserId : req.params.id }, fields: ["numberOfDays", "receiptDays"], limit:1 })
                                ]);
                            break;               
                        default:
                            throw { message: txtGeneral.serverError };
                    }
                    await creaUserJson(req.params.id);// besoin d'attendre car utiliser pour rafraîchir l'affichage
                    res.status(200).json({ message: messageRetour });
                }
            }
        }
        next();
    }
    catch(e)
    {
        const returnAPI=toolError.returnSequelize(e);
        if(returnAPI.messages)
        {
            res.status(returnAPI.status).json({ errors : returnAPI.messages });
            next();
        }
        else
            next(e);
    }
}

exports.checkNewLoginLink = async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const userDatas=await checkTokenUser(req.params.token);        
        if(userDatas)
        {
            if(userDatas.User.id!==req.connectedUser.User.id)
                res.status(401).json({ errors: [txtGeneral.notAllowed] });
            else
            {
                await db["User"].update({ ...userDatas.decodedToken }, { where: { id : userDatas.User.id } , fields: ["email", "password"], limit:1 });
                creaUserJson(userDatas.User.id);
                res.status(200).json({ message: txt.mailUpdateLoginOkMessage });
            }
        }
        else
            res.status(404).json({ errors: [txtGeneral.notAllowed] });
        next();
    }
    catch(e)
    { 
        next(e);
    }
}

exports.create = async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const connectedUser=req.connectedUser;
        if(connectedUser.User.status==="user")
            res.status(403).json({ errors: [txtGeneral.notAllowed] });
        else
        {
            if(!tool.isEmpty(req.body.GodfatherId) && ! await searchUserById(req.body.GodfatherId))
                res.status(400).json({ errors: [txt.updatedFailedGodfatherNotFound] });
            else
            {
                req.body.password=await bcrypt.hash(req.body.password, config.bcryptSaltRounds);
                if(req.body.GodfatherId==="")
                    req.body.GodfatherId=null;
                let user;
                switch(connectedUser.User.status)
                {
                    case "admin":
                        user=await db["User"].create({ ...req.body });
                        req.body.UserId=user.id;
                        await db["Subscription"].create({ ...req.body });
                        break;
                    case "manager":
                        user=await db["User"].create({ ...req.body }, { fields: ["name", "email", "password", "language", "adminComments", "smtp", "newsletterOk", "GodfatherId"] });
                        req.body.UserId=user.id;
                        await db["Subscription"].create({ ...req.body }, { fields: ["numberOfDays", "receiptDays", "noticeOk", "UserId"] });
                        break;
                    case "creator":
                        user=await db["User"].create({ ...req.body }, { fields: ["name", "email", "password", "language", "adminComments"] });
                        req.body.UserId=user.id;
                        await db["Subscription"].create({ ...req.body }, { fields: ["numberOfDays", "receiptDays", "UserId"] });
                        break;                       
                    default:
                        throw { message: [txtGeneral.serverError] };
                }
                creaUserJson(req.body.UserId);
                res.status(201).json({ message: [txt.creationOkMessage], id:req.body.UserId });
            }
        }
        next();
    }
    catch(e)
    {
        const returnAPI=toolError.returnSequelize(e);
        if(returnAPI.messages)
        {
            res.status(returnAPI.status).json({ errors : [returnAPI.messages] });
            next();
        }
        else
            next(e);
    }
}

exports.validate = async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const connectedUser=req.connectedUser;
        if(["admin", "manager"].indexOf(connectedUser.User.status) === -1)
            res.status(403).json({ errors: [txtGeneral.notAllowed] });
        else
        {
            const userToValidating=await searchUserById(req.params.id);
            if(!userToValidating)
                res.status(404).json({ errors: [txt.notFound] });
            else  if(userToValidating.Subscription)
                res.status(400).json({ errors: [txt.validationAlreadyMessageAdmin] });
            else
            {
                await db["Subscription"].create({ numberOfDays: config.freeAccountTimingInDays, UserId: req.params.id });
                creaUserJson(req.params.id);
                res.status(200).json({ message: [txt.validationMessageAdmin] });
            }
        }
    }
    catch(e)
    {
        const returnAPI=toolError.returnSequelize(e);
        if(returnAPI.messages)
        {
            res.status(returnAPI.status).json({ errors : returnAPI.messages });
            next();
        }
        else
            next(e);
    }
}

exports.delete = async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const connectedUser=req.connectedUser;
        if(connectedUser.User.status==="user")
        {
            const userDatas=await searchUserById(connectedUser.User.id);
            const token=jwt.sign({ userId: connectedUser.User.id }, config.tokenPrivateKey, { expiresIn: config.tokenDeleteUserTimeInHours });            
            const mapMail =
            {
               USER_NAME: userDatas.User.name,
               LINK_URL : config.siteUrl+"/"+configTpl.deleteLinkPage+token
            };
            const mailDatas=
            {
                mailSubject: txt.mailDeleteSubject,
                mailPreheader: txt.mailDeleteSubject,
                mailTitle: txt.mailDeleteSubject,
                mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.deleteLinkPage+token,
                mailHeaderLinkTxt: txt.mailDeleteLinkTxt,
                mailMainContent: tool.replaceAll(txt.mailDeleteBodyHTML, mapMail),
                linksCTA: [{ url:config.siteUrl+"/"+configTpl.deleteLinkPage+token, txt:txt.mailDeleteLinkTxt }],
                mailRecipientAddress: connectedUser.User.email
            }
            await toolMail.sendMail(connectedUser.User.smtp, connectedUser.User.email, txt.mailDeleteSubject, tool.replaceAll(txt.mailDeleteBodyTxt, mapMail), "", mailDatas);
            res.status(200).json({ message: txt.mailDeleteLinkMessage });
        }
        else if (["admin","manager","creator"].indexOf(connectedUser.User.status) !== -1)
        {
            const userDeleted=await searchUserById(req.params.id);
            if(!userDeleted)
                throw { message: txt.notFound };
            const nb=await db["User"].destroy({ where: { id : req.params.id }, limit:1 });
            if(nb===1)
            {
                await toolFile.deleteJSON(config.dirCacheUsers, req.params.id);
                toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/without", req.params.id);
                toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/answers", req.params.id);
                toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/answers", "stats"+req.params.id);
                let now=new Date(), wasValided=false, wasPremium=false;
                if(userDeleted.Subscription)
                {
                    wasValided=true;
                    if(userDeleted.Subscription.numberOfDay > config.freeAccountTimingInDays)
                        wasPremium=true;
                }
                db["UserDeleted"].create({ createdAt: userDeleted.User.createdAt, deletedAt: now, wasValided: wasValided, wasPremium: wasPremium });
                res.status(200).json({ message: [txt.deleteOkMessage] });
            }
            else
            {
                res.status(400).json({ errors: txtGeneral.serverError });
                throw { message: txt.deleteFailMessage+req.params.id };
            }
        }
        next();
    }
    catch(e)
    {
        next(e);
    }        
}

exports.checkDeleteLink = async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const connectedUser=req.connectedUser;
        const userDatas=await checkTokenUser(req.params.token);
        if(userDatas)
        {
            if(connectedUser.User.id!=userDatas.User.id)
                res.status(403).json({ errors: [txtGeneral.notAllowed] });
            else
            {       
                const nb=await db["User"].destroy({ where: { id : connectedUser.User.id }, limit:1 });
                if(nb===1)
                {
                    await toolFile.deleteJSON(config.dirCacheUsers, connectedUser.User.id);
                    res.status(200).json({ message: txt.mailDeleteLinkOkMessage });
                    toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/without", connectedUser.User.id);
                    toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/answers", connectedUser.User.id);
                    toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/answers", "stats"+connectedUser.User.id);
                    const now=new Date(); wasPremium=false;
                    if(userDatas.Subscription.numberOfDay > config.freeAccountTimingInDays)
                        wasPremium=true;
                    db["UserDeleted"].create({ createdAt: userDatas.User.createdAt, deletedAt: now, wasValided:true, wasPremium: wasPremium });
                }
                else
                    res.status(400).json({ errors: [txt.mailDeleteLinkAlreadyMessage] });
            }
        }
        else
            res.status(400).json({ errors: [txt.mailDeleteLinkFailMessage] });
        next();
    }
    catch(e)
    { 
        next(e);
    }
}

exports.getOneUserById = async (req, res, next) =>
{
    try
    { 
        const connectedUser=req.connectedUser;
        if(connectedUser.User.status==="user")
        {
            if(connectedUser.User.id!=req.params.id)
                res.status(403).json({ errors: txtGeneral.notAllowed });
            else
            {
                const datas=await searchUserById(connectedUser.User.id);
                if(datas)
                    res.status(200).json(datas);
                else
                    res.status(404).json({ message:txt.notFound });
            }
        }
        else if (["admin","manager","creator"].indexOf(connectedUser.User.status) !== -1)
        {// dans le cas des "creator" il faudra peut-être limité l'accès à "ses" utilisateurs ?
            const datas=await searchUserById(req.params.id);
            if(datas)
                res.status(200).json(datas);
            else
                res.status(404).json({ message:txt.notFound });
        }
        next();     
    }
    catch(e)
    { 
        next(e);
    }
}

exports.getOneUserGodChilds = async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const connectedUser=req.connectedUser;
        let godchilds;
        if(["admin","manager","creator"].indexOf(connectedUser.User.status) === -1)
            godchilds=await db["User"].findAll({ where: { GodfatherId: connectedUser.User.id }, attributes: ["id", "name", "email"] });
        else
            godchilds=await db["User"].findAll({ where: { GodfatherId: req.params.id }, attributes: ["id", "name", "email"] });
        res.status(200).json(godchilds);
        next();
     }
    catch(e)
    { 
        next(e);
    }
}

exports.searchUsers = async (req, res, next) =>
{
    try
    {
        const connectedUser=req.connectedUser;
        if(["admin", "manager", "creator"].indexOf(connectedUser.User.status) === -1)
            res.status(403).json({ errors: [txtGeneral.notAllowed] });
        else
        {
            const db = require("../models/index");
            const Users=await db.sequelize.query("SELECT `id`,`name`,`email` FROM `Users` WHERE  `name` LIKE :search OR `adminComments` LIKE :search OR `email` LIKE :search OR `id` = :id", { replacements: { search: "%"+req.body.search+"%", id: req.body.search }, type: QueryTypes.SELECT  });
            res.status(200).json(Users);
        }
        next();
    }
    catch(e)
    {
        next(e);
    }
}

// Retourne des statistiques concernant les utilisateurs
// Utile tableau de bord gestionnaires
exports.getStats= async (req, res, next) =>
{
    try
    {
        let stats=await getStatsUsers();
        stats.Answers=await answerCtrl.getStatsAnswers();
        stats.Subscriptions=await subscriptionCtrl.getStatsSubscriptions();
        res.status(200).json(stats);
        next();
    }
    catch(e)
    { 
        next(e);
    }
}

// CRONS
// Supprime les fichiers stockant les erreurs de connexion ayant expirés
exports.deleteLoginFail = async (req, res, next) =>
{
    try
    {
        const fileExpiration=new Date().getTime()-config.loginFailTimeInMinutes*60*1000;
        const deleteFiles = await toolFile.deleteOldFilesInDirectory(config.dirTmpLogin, fileExpiration);
        res.status(200).json(deleteFiles);
        next();
    }
    catch(e)
    { 
        next(e);
    }
}

// Suppression des fichiers pour des utilisateurs n'existant plus
exports.deleteJsonFiles= async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const users=await db["User"].findAll({ attributes: ["id"] });
        let saveFiles=[];
        for(let i in users)
        {
            saveFiles.push(users[i].id+".json");
            saveFiles.push("stats"+users[i].id+".json");
        }
        const deleteFiles = await Promise.all([
            toolFile.deleteFilesInDirectory(configUsers.dirCacheUsers, saveFiles),
            toolFile.deleteFilesInDirectory(configUsers.dirCacheUsersWithoutAnswers, saveFiles),
            toolFile.deleteFilesInDirectory(configUsers.dirCacheUsersAnswers, saveFiles)
        ]);
        res.status(200).json(deleteFiles);
        next();
    }
    catch(e)
    { 
        next(e);
    }
}

// Suppression des comptes n'ayant pas été validé passé un certain délai
exports.deleteUnvalided= async (req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const nowTS=Date.now();
        const timeExpiration=nowTS-parseInt(config.tokenSignupValidationTimeInHours,10)*3600*1000;
        const userUnvalided= await db.sequelize.query("SELECT createdAt FROM `Users` WHERE UNIX_TIMESTAMP(createdAt) < "+timeExpiration+" AND `id` NOT IN (SELECT `UserId` FROM `Subscriptions`)", { type: QueryTypes.SELECT });
        if(userUnvalided.length!==0)
        {
            const [results, metadata] = await db.sequelize.query("DELETE FROM `Users` WHERE UNIX_TIMESTAMP(createdAt) < "+timeExpiration+" AND `id` NOT IN (SELECT `UserId` FROM `Subscriptions`)");
            const now=new Date();
            for(let i in userUnvalided)
                await db["UserDeleted"].create({ createdAt: userUnvalided[i].createdAt, deletedAt: now, wasValided: false });
            res.message=metadata.affectedRows+txt.cronDeleteUnvalidedUsersMessage;
        }
        res.status(200).json(true);
        next(); 
    }
    catch(e)
    { 
        next(e);
    }
}

// Suppression des comptes dont l'abonnement a expiré et ne s'étant pas connecté depuis à certains temps
exports.deleteInactiveAccounts= async(req, res, next) =>
{
    try
    {
        const db = require("../models/index");
        const usersInactive= await db.sequelize.query("SELECT createdAt FROM `Users` WHERE status='user' AND (ADDDATE(`connectedAt`, "+config.inactiveAccountTimeToDeleteInDays+") < NOW() OR `connectedAt` IS NULL) AND `id` IN (SELECT `UserId` FROM `Subscriptions` WHERE ADDDATE(`createdAt`, `numberOfDays`)< NOW())", { type: QueryTypes.SELECT });
        if(usersInactive.length!==0)
        {
            const [results, metadata] = await db.sequelize.query("DELETE FROM `Users` WHERE (ADDDATE(`connectedAt`, "+config.inactiveAccountTimeToDeleteInDays+") < NOW() OR `connectedAt` IS NULL) AND status='user' AND `id` IN (SELECT `UserId` FROM `Subscriptions` WHERE ADDDATE(`createdAt`, `numberOfDays`)< NOW())");
            if(metadata.affectedRows!==0)
               res.message=metadata.affectedRows+txt.deleteInactiveUsersMessage;
            const now=new Date();
            for(let i in usersInactive)
                await db["UserDeleted"].create({ createdAt: usersInactive[i].createdAt, deletedAt: now, wasValided: true });
        }
        res.status(200).json(true);
        next();
    }
    catch(e)
    {
        next(e);
    }
}

// FONCTIONS UTILITAIRES

// Création du fichier des données de l'utilisateur + son abonnement et ses pauses
const creaUserJson = async (id) =>
{
    id=tool.trimIfNotNull(id);
    if(id===null)
        return false;
    const db = require("../models/index");
    const User=await db["User"].findByPk(id, { attributes: { exclude: ['password'] } });
    if(User)
    {
        let datas={ User };
        const Subscription=await db["Subscription"].findOne({ where: { UserId: id } });
        if(Subscription) // pas de cache pour utilisateurs non validés
        {
            datas.Subscription=Subscription;
            const Pauses=await db["Pause"].findAll({ where: { SubscriptionId: Subscription.id } });
            if(Pauses)
                datas.Pauses=Pauses;
            await toolFile.createJSON(config.dirCacheUsers, id, datas);
        }
        return datas;
    }
    else
        return false;
}
exports.creaUserJson = creaUserJson;

const searchUserById = async (id) =>
{
    id=tool.trimIfNotNull(id);
    if(id===null)
        return false;
    const user=await toolFile.readJSON(config.dirCacheUsers, id);
    if(user)
        return user;
    else
        return await creaUserJson(id);
}
exports.searchUserById = searchUserById;

const searchUserByEmail = async (email) =>
{
    const db = require("../models/index");
    const emailSend=tool.trimIfNotNull(email);
    const userEmail=await db["User"].findOne({ attributes: ["id"], where: { email : emailSend } });
    if(userEmail)
        return await searchUserById(userEmail.id);// permet de récupérer les autres infos (Subscription, etc.)
    else
        return false;
}

// Recherche le parrain dont le "code" fourni peut être l'identifiant préfixé ou bien l'adresse e-mail
const searchIdGodfather = async (code) =>
{
    let godfatherInfos="";
    code=tool.trimIfNotNull(code);
    if(code===null)
        return false;
    else if(code.indexOf("@")!==-1) // tester en premier, car une adresse peut débuter par le préfixe de l'appli...
        godfatherInfos=await searchUserByEmail(code);
    else if (code.startsWith(config.beginCodeGodfather))
        godfatherInfos=await searchUserById(code.substring(config.beginCodeGodfather.length));
    if(godfatherInfos.Subscription && godfatherInfos.User.status==="user") // le parrain doit avoir validé son compte et doit être un simple "user"
        return godfatherInfos.User;
    else
        return null;
}

// Envoi le lien permettant au nouvel utilisateur de valider son compte
const sendValidationLink = async (user) =>
{
    const token=jwt.sign({ userId: user.id }, config.tokenPrivateKey, { expiresIn: config.tokenSignupValidationTimeInHours });
    const mapMail =
    {
       USER_NAME: user.name,
       LINK_URL: config.siteUrl+"/"+configTpl.validationLinkPage+token,
    };
    const mailDatas=
    {
        mailSubject: txt.mailValidationLinkSubject,
        mailPreheader: txt.mailValidationLinkSubject,
        mailTitle: txt.mailValidationLinkSubject,
        mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.validationLinkPage+token,
        mailHeaderLinkTxt: txt.mailValidationLinkTxt,
        mailMainContent: tool.replaceAll(txt.mailValidationLinkSBodyHTML, mapMail),
        linksCTA: [{ url:config.siteUrl+"/"+configTpl.validationLinkPage+token, txt:txt.mailValidationLinkTxt }],
        mailRecipientAddress: user.email
    }
    await toolMail.sendMail(0, user.email, txt.mailValidationLinkSubject, tool.replaceAll(txt.mailValidationLinkSBodyTxt, mapMail), "", mailDatas);
    return true;
}

// Permet de vérifier un token de connexion : est-il valide et est-ce qu'il correspond à un utilisateur ?
// Si oui, on peut aussi enregistrer une nouvelle connexion pour l'utilisateur (une fois / jour)
const checkTokenUser = async (token) =>
{
    const db = require("../models/index");
    const decodedToken=jwt.verify(token, config.tokenPrivateKey);
    const userId=decodedToken.userId;
    const datas=await searchUserById(userId);
    if(datas.User)
    {
        const now=new Date();
        const nowTS=Date.now()
        const lastConnection = Date.parse(datas.User.connectedAt);
        if(lastConnection+24*3600*1000 < nowTS) // une fois par jour suffit.
        {
            await db["User"].update({ connectedAt: now }, { where: { id : userId }, limit:1 });
            creaUserJson(userId);
        }
        datas.decodedToken=decodedToken;
        return datas;
    }
    else
        return false;
}
exports.checkTokenUser = checkTokenUser;

// Stats sur les créations de compte, suppression.. durant les dernières 24H ou depuis le début
const getStatsUsers = async () =>
{
    const db = require("../models/index");
    const getNewUsers24H = await db.sequelize.query("SELECT `id` FROM `Users` WHERE `createdAt` > ADDDATE(NOW(), -1)", { type: QueryTypes.SELECT });
    const getNewUsersTot = await db.sequelize.query("SELECT `id` FROM `Users`", { type: QueryTypes.SELECT });
    const getDeletedUsers24H = await db.sequelize.query("SELECT `id` FROM `UserDeleteds` WHERE `deletedAt` > ADDDATE(NOW(), -1)", { type: QueryTypes.SELECT });
    const getDeletedUsersTot = await db.sequelize.query("SELECT `id` FROM `UserDeleteds`", { type: QueryTypes.SELECT });
    const getDeletedUsersWasValided = await db.sequelize.query("SELECT `id` FROM `UserDeleteds` WHERE `wasValided`= 1", { type: QueryTypes.SELECT });
    const getDeletedUsersTotWasPremium = await db.sequelize.query("SELECT `id` FROM `UserDeleteds` WHERE `wasPremium`= 1", { type: QueryTypes.SELECT });
    if(getNewUsers24H && getNewUsersTot &&  getDeletedUsers24H && getDeletedUsersWasValided && getDeletedUsersTotWasPremium && getDeletedUsersTot )
    {
        const stats =
        {
            nbNewUsers24H : getNewUsers24H.length,
            nbNewUsersTot : getNewUsersTot.length,
            nbDeletedUsers24H : getDeletedUsers24H.length,
            nbDeletedUsersTot : getDeletedUsersTot.length,
            nbDeletedUsersWasValided : getDeletedUsersWasValided.length,
            nbDeletedUsersTotWasPremium : getDeletedUsersTotWasPremium.length
        }
        return stats;
    }
    else
        return false; 
}
exports.getStatsUsers = getStatsUsers;