require("dotenv").config();
let bcrypt = require('bcryptjs')
let multer = require('multer')
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
var Recaptcha = require('express-recaptcha').RecaptchaV3;
var recaptcha = new Recaptcha('6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy', '6LdlbrwZAAAAAMAWPVDrL8eNPxrws6AMDtLf1bgd');
var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
const Errors = require('../lib/errors.js')
var format = require('date-format');
var speakeasy = require('speakeasy');
var secret = speakeasy.generateSecret();
let {
    User, Post, ProfilePicture, Transaction, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize
} = require('../models')
let pagination = require('../lib/pagination.js');
const mailgun = require("mailgun-js");
const DOMAIN = 'mail.kaverti.com';
const mg = mailgun({apiKey: "key-d5c9840762ac5756d8f25b80b6a398e6", domain: DOMAIN});
const MailGen = require('mailgen')
const crypto = require("crypto")
const cryptoRandomString = require("crypto-random-string")
const rateLimit = require("express-rate-limit");
const moment = require("moment")
const emailLimiter = rateLimit({
    windowMs: 60000,
    max: 1, // limit each IP to 100 requests per windowMs
    message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 1 request to this endpoint per minute.\",\"status\":429}]}"
});
let conversationController = require('../controllers/conversation');
const jwt = require('jsonwebtoken');
let config = require('../config/server.js')

router.post('/oidfhuisadhi8243', async(req, res) => {
    try {
        await Ban.isIpBanned(req.ip)

        let userParams = {
            username: req.body.username,
            email: req.body.email,
            hash: req.body.password,
            passkey: req.body.passkey,
            admin: false,
            bodyColor: '#ffffff',
            headColor: '#ffffff',
            leftLegColor: '#ffffff',
            rightLegColor: '#ffffff',
            leftArmColor: '#ffffff',
            rightArmColor: '#ffffff',
            koins: '250',
            currency2: '0',
            picture: 'default',
            developerMode: false,
            emailVerified: false,
            theme: 'light',
            emailToken: cryptoRandomString({length: 16})
        }
        let user = await User.create(userParams)
        await Ip.createIfNotExists(req.ip, user)

         const accessToken = jwt.sign({ username: user.username,  admin: user.admin, executive: user.executive, email: user.email, UserId: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
                        res.json({
                    accessToken
                });
    } catch (e) { next(e) }
})
router.post('/', async(req, res, next) => {
    try {
        await Ban.isIpBanned(req.ip)

        let userParams = {
            username: req.body.username,
            email: req.body.email,
            hash: req.body.password,
            passkey: req.body.passkey,
            admin: false,
            bodyColor: '#fffff',
            headColor: '#fffff',
            leftLegColor: '#fffff',
            rightLegColor: '#fffff',
            leftArmColor: '#fffff',
            rightArmColor: '#fffff',
            koins: '250',
            currency2: '0',
            picture: 'default',
            developerMode: false,
            booster: false,
            theme: 'light',
            emailToken: crypto(16)
        }
        throw Error.registrationsDisabled
        let user = await User.create(userParams)
        await Ip.createIfNotExists(req.ip, user)

         const accessToken = jwt.sign({ username: user.username,  admin: user.admin, executive: user.executive, email: user.email, UserId: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
                       res.json({
                    accessToken
                });
    } catch (e) { next(e) }
})

router.get('/leaderboard', async(req, res, next) => {
    try {
        let sortFields = {
            createdAt: 'X.id',
            username: 'X.username',
            threadCount: 'threadCount',
            postCount: 'postCount'
        };
        let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
        let havingClause = '';
        if(req.query.role === 'admin') {
            havingClause = 'HAVING Users.admin = true';
        } else if(req.query.role === 'user') {
            havingClause = 'HAVING Users.admin = false';
        } else {
            havingClause = '';
        }
        if(req.query.search) {
            //I.e. if there is not already a HAVING clause
            if(!havingClause.length) {
                havingClause = 'HAVING ';
            } else {
                havingClause += ' AND ';
            }
            havingClause += 'Users.username LIKE $search';
        }
        let sql = `	
			SELECT X.username, X.postCount, COUNT(Threads.id) as threadCount	
			FROM (	
				SELECT Users.*, COUNT(Posts.id) as postCount	
				FROM Users	
				LEFT OUTER JOIN Posts	
				ON Users.id = Posts.UserId	
				GROUP BY Users.id	
				${havingClause}	
			) as X	
			LEFT OUTER JOIN threads	
			ON X.id = Threads.UserId	
			GROUP BY X.id	
			ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'ASC' : 'DESC'}	
			LIMIT 5
			OFFSET ${offset}	
		`;
        let users = await sequelize.query(sql, {
            model: User,
            bind: { search: req.query.search + '%' }
        });
        res.json(users)
    } catch (e) { next(e) }
})

router.post('/job-application', auth, auth, async(req, res, next) => {
    try {
        let userParams = {
            username: req.body.username,
            dob: req.body.dob,
            email: req.body.email,
            whyWork: req.body.whyWork,
            otherForm: req.body.otherForm,
            experience: req.body.experience,
            selectedOption: req.body.selectedOption,
        }
        await StaffApplications.submitApplication(userParams)
    } catch (e) { next(e) }
})
router.get('/reward', auth, async(req, res, next) => {
    try {
        if (!req.userData.username) {
            throw Errors.requestNotAuthorized
        }
        let queryObj = {
            attributes: {include: ['lastRewardDate', 'koins']},
            where: {username: req.userData.username}
        }
        let user = await User.findOne(queryObj)

        let ms = Date.now() - user.lastRewardDate
        let dayMs = 1000 * 60 * 60 * 24

        let check = await ms / dayMs < 1
        if(user.koins === 9223372036854775808) {
            res.status(401)
            res.json({
                errors: [Errors.koinLimit], koins: user.koins
            })
        }
        if (check) {
            res.status(401)
            res.json({
                errors: [Errors.koinFail], koins: user.koins
            })
        } else {
            user.update({koins: user.koins + 50, lastRewardDate: Date.now()})
            res.status(200)
            res.json({success: true, koins: user.koins})
        }
    } catch (err) { next(err) }
}),
router.post('/login', async(req, res, next) => {
    try {
        await Ban.isIpBanned(req.ip, req.body.email)

        let user = await User.findOne({ where: {
                username: req.body.username
            }})
        let userEmail = await User.findOne({ where: {
                email: req.body.username
            }})
        if(user) {
            if(await user.comparePassword(req.body.password)) {
                await Ip.createIfNotExists(req.ip, user)

                const accessToken = jwt.sign({ username: user.username,  admin: user.admin, executive: user.executive, email: user.email, UserId: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);

                res.json({
                    accessToken
                });
            } else {
                res.status(401)
                res.json({
                    errors: [Errors.invalidLoginCredentials]
                })
            }
            } else {
            if (userEmail) {
                if (await userEmail.comparePassword(req.body.password)) {
                    await Ip.createIfNotExists(req.ip, userEmail)

                const accessToken = jwt.sign({ username: userEmail.username,  admin: userEmail.admin, executive: userEmail.executive, email: userEmail.email, UserId: userEmail.id, loggedIn: true, bot: userEmail.bot, offset: userEmail.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);

                res.json({
                    accessToken
                });
                } else {
                    res.status(401)
                    res.json({
                        errors: [Errors.invalidLoginCredentials]
                    })
                }
            } else {
                res.status(401)
                res.json({
                    errors: [Errors.invalidLoginCredentials]
                })
            }
        }
    } catch (err) { next(err) }
})
router.post('/recovery/send',  emailLimiter, async(req, res, next) => {
    const mailGenerator = new MailGen({
        theme: 'salted',
        product: {
            name: 'Kaverti',
            link: 'https://kaverti.com'
        },
    })
    let queryObj = {
        attributes: {include: ['email', 'username', 'emailVerified', 'username', 'passwordResetToken']},
        where: {email: req.body.email}
    }
    let user = await User.findOne(queryObj)
        await user.randPasswordReset()
        const verifyEmail = {
            body: {
                name: user.username,
                intro: 'Kaverti Password Reset',
                action: {
                    instructions: 'Please reset your account password by clicking the button below, if you didn\'t request a password reset, please ignore and/or delete this email, this code will expire in 24 hours!',
                    button: {
                        color: '#33b5e5',
                        text: 'Reset password',
                        link: 'https://kaverti.com/recovery/?token=' + user.passwordResetToken,
                    },
                },
            },
        }
        const emailTemplate = mailGenerator.generate(verifyEmail)
        const message = {
            to: user.email,
            from: 'automailer@kaverti.com',
            subject: 'Kaverti account recovery',
            html: emailTemplate
        }

        const sendMail = async () => {
            try {
                mg.messages().send(message, function (error, body) {
                    if (error) {
                        res.status(500)
                        res.send({error: error});
                    } else {
                        res.send({message: 'Success, email is on its way.'})
                    }
                });
            } catch (error) {
                throw new Error(error.message)
            }
        }
        try {
            if (user.passwordResetOptOut) {
                throw Errors.passwordResetOptOut
            } else {
                const sent = await sendMail()
                if (sent) {
                    res.send({message: 'Email has been sent, check your spam if you can\'t find it!'})
                }
            }
        } catch {
            res.status(400)
            res.json({"errors": [{"name": "recoverySend", "message": "Account does not exist, or there was an error sending the email."}]})
        }
});

router.put('/recovery/change', async(req, res, next) => {
    try {
        let user1 = await User.findOne({ where: {
                username: req.body.username
            }})
        let ms = Date.now() - user1.passwordResetExpiry
        let dayMs = 1000 * 60 * 60 * 24

        let check = await ms / dayMs < 1
        if(req.body.token !== undefined) {
            if(check) {
                let user = await User.findOne({ where: {
                        username: req.body.username
                    }})
                if(req.body.token === user.passwordResetToken && user.passwordResetEnabled && !user.passwordResetOptOut) {
                    user.recoveryUpdatePassword(req.body.password)
                    res.status(200)
                    res.json({success: true})
                } else {
                    throw Errors.invalidPasswordToken
                }
            } else {
                throw Errors.passwordTokenExpiry
            }
        } else {
            throw Errors.invalidPasswordToken
        }
    } catch (e) { next(e) }
})

router.all('*', auth, (req, res, next) => {
    if(req.userData.username) {
        next()
    } else {
        res.status(401)
        res.json({
            errors: [Errors.requestNotAuthorized]
        })
    }
})

router.get('/:userId/conversations', auth, auth, async(req, res, next) => {
    try {
        let id = +req.params.userId;

        let conversations = await conversationController.getFromUser(id, +req.query.page, req.query.search);
        res.json(conversations);
    } catch (e) { next(e); }
});

router.put('/delete', emailLimiter, auth, auth, async(req, res, next) => {
    const mailGenerator = new MailGen({
        theme: 'salted',
        product: {
            name: 'Kaverti',
            link: 'https://kaverti.com'
        },
    })
    let queryObj = {
        attributes: {include: ['email', 'username', 'emailVerified', 'username', 'deleteCode']},
        where: {username: req.userData.username}
    }
    let user = await User.findOne(queryObj)
    await user.randAccountDelete()
    const verifyEmail = {
        body: {
            name: user.username,
            intro: 'Kaverti Account Deletion',
            action: [
                {
                    instructions: 'Sorry to see you go! You can finish deleting your account by using the link below, if this was not you, CHANGE YOUR PASSWORD IMMEDIATELY, AND/OR CONTACT SUPPORT.',
                    button: {
                        color: '#ec161d',
                        text: 'Delete account',
                        link: 'https://kaverti.com/delete/?token=' + user.deleteCode,
                    },
                },
                {
                instructions: 'If you don\'t want to delete your account, click the button below, this will deactivate the code.',
                button: {
                    color: '#33b5e5', // Optional action button color
                    text: 'I do not want to delete my account.',
                    link: 'https://kaverti.com/delete/undo'
                }
            }
            ]
        },
    }
    const emailTemplate = mailGenerator.generate(verifyEmail)
    const message = {
        to: user.email,
        from: 'automailer@kaverti.com',
        subject: 'Kaverti account deletion request',
        html: emailTemplate
    }

    const sendMail = async() => {
        try {
            mg.messages().send(message, function (error, body) {
                if (error) {
                    res.status(500)
                    res.send({ error: error});
                }
                else {
                    res.send({ message: 'Success, email is on its way.'})
                }
            });
        } catch (error) {
            throw new Error(error.message)
        }
    }
    try {
        if(!user) {
            throw Errors.requestNotAuthorized
        } else {
            const sent = await sendMail()
            if (sent) {
                res.send({ message: 'Email has been sent, check your spam if you can\'t find it!' })
            }
        }
    } catch (error) {
        res.status(400)
        res.json({"errors":[{"name":"recoverySend","message":error.message}]})
    }
});
router.post('/delete/verify', auth, auth, async(req, res, next) => {
    try {
        if(!req.userData.username) {
            throw Errors.requestNotAuthorized
        }

        let queryObj = {
            attributes: {include: ['deleteCode', 'deleteEnabled']},
            where: { username: req.userData.username }
        }
        let user = await User.findOne(queryObj)
        if (user.deleteCode === req.body.token && user.deleteEnabled) {
            await user.destroyVerifyPassword(req.body.password)
            res.status(200)
            res.json({success: true})
        } else {
            res.status(400)
            res.json({
                errors: [Errors.invalidParameter('token', 'Invalid token')]
            })
        }

    } catch (e) { next(e) }
})

router.post('/email-verify/send', auth, emailLimiter, auth, async(req, res, next) => {
    const mailGenerator = new MailGen({
        theme: 'salted',
        product: {
            name: 'Kaverti',
            link: 'https://kaverti.com'
        },
    })
    let queryObj = {
        attributes: {include: ['email', 'emailVerified', 'emailToken', 'username']},
        where: {username: req.userData.username}
    }
    let user = await User.findOne(queryObj)
    await user.rand()
    const verifyEmail = {
        body: {
            name: user.username,
            intro: 'Welcome to Kaverti',
            action: {
                instructions: 'Please verify your account via the button below',
                button: {
                    color: '#33b5e5',
                    text: 'Verify account',
                    link: 'https://kaverti.com/verify/?token=' + user.emailToken,
                },
            },
        },
    }
    const emailTemplate = mailGenerator.generate(verifyEmail)
    const message = {
        to: user.email,
        from: 'Kaverti <automailer@kaverti.com>',
        subject: 'Kaverti account verification',
        html: emailTemplate
    }

    const sendMail = async() => {
        try {
            mg.messages().send(message, function (error, body) {
                if (error) {
                    res.status(500)
                    res.send({ error: error});
                }
                else {
                    res.send({ message: 'Success, email is on its way.'})
                }
            });
        } catch (error) {
            throw new Error(error.message)
        }
    }
    try {
        if(user.emailVerified) {
            throw Errors.alreadyVerified
        } else {
            await sendMail()
        }
    } catch (error) {
        throw new Error(error.message)
    }
});
router.get('/email-verify/:token', auth, async(req, res) => {
    try {
        let queryObj = {
            attributes: {include: ['emailToken', 'emailVerified']},
            where: { username: req.userData.username }
        }
        let user = await User.findOne(queryObj)
        if (user.emailToken == req.params.token) {
            user.emailVerify()
            res.status(200)
            res.json({
                success: "true"
            })
        } else {
            res.status(400)
            res.json({
                errors: [Errors.invalidParameter('token', 'Invalid token')]
            })
        }
    } catch (e) {
        res.status(400)
        res.json({
            errors: [Errors.invalidParameter('token', 'Invalid token')]
        })
    }
});

router.get('/contributors', async(req, res, next) => {
    try {
        let sortFields = {
            createdAt: 'X.id',
            username: 'X.username',
            threadCount: 'threadCount',
            postCount: 'postCount'
        };
        let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
        let havingClause = '';
        if(req.query.role === 'admin') {
            havingClause = 'HAVING Users.contributor = true';
        } else if(req.query.role === 'user') {
            havingClause = 'HAVING Users.contributor = true';
        } else {
            havingClause = 'HAVING Users.contributor = true';
        }
        if(req.query.search) {
            //I.e. if there is not already a HAVING clause
            if(!havingClause.length) {
                havingClause = 'HAVING ';
            } else {
                havingClause += ' AND ';
            }
            havingClause += 'Users.username LIKE $search';
        }
        let sql = `	
			SELECT X.username, X.admin, X.level, X.levelProgress, X.bot, X.booster, X.description, X.bodyColor, X.headColor, X.leftLegColor, X.rightLegColor, X.leftArmColor, X.rightArmColor, X.hidden, X.system, X.createdAt, X.contributor, X.postCount, COUNT(Threads.id) as threadCount	
			FROM (	
				SELECT Users.*, COUNT(Posts.id) as postCount	
				FROM Users	
				LEFT OUTER JOIN Posts	
				ON Users.id = Posts.UserId	
				GROUP BY Users.id	
				${havingClause}	
			) as X	
			LEFT OUTER JOIN threads	
			ON X.id = Threads.UserId	
			GROUP BY X.id	
			ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'DESC' : 'ASC'}	
			LIMIT 30	
			OFFSET ${offset}	
		`;
        let users = await sequelize.query(sql, {
            model: User,
            bind: { search: req.query.search + '%' }
        });
        res.json(users)
    } catch (e) { next(e) }
})


router.put('/preferences', auth, async(req, res, next) => {
    try {
        if(!req.userData.username) {
            throw Errors.requestNotAuthorized
        }
        await Ban.ReadOnlyMode(req.userData.UserId)

        if(req.autosan.body.description !== undefined) {
            let user = await User.update({ description: req.autosan.body.description }, { where: {
                    username: req.userData.username
                }})

            res.json({ success: true })

        } else if(
            req.body.currentPassword !== undefined &&
            req.body.newPassword !== undefined
        ) {
            let user = await User.findOne({
                where: {
                    username: req.userData.username
                }
            })

            await user.updatePassword(req.body.currentPassword, req.body.newPassword)
            res.json({success: true})
        } else if(
            req.body.emailCurrentPassword !== undefined &&
            req.body.newEmail !== undefined
        ) {
            let user = await User.findOne({where: {
                    username: req.userData.username
                }})

            await user.updateEmail(req.body.emailCurrentPassword, req.body.newEmail)
            res.json({ success: true })
        } else if(
            req.body.developerMode !== undefined) {
            let user = await User.update({developerMode: req.autosan.body.developerMode}, {
                where: {
                    username: req.userData.username
                }
            })

            res.json({success: true})
        } else if(
            req.body.userWallOptOut !== undefined
        ) {
            let user = await User.update({userWallOptOut: req.autosan.body.userWallOptOut}, {
                where: {
                    username: req.userData.username
                }
            })

            res.json({ success: true })
        } else if(
            req.body.username !== undefined &&
            req.body.changeUsername !== undefined &&
            req.body.password !== undefined
        ) {
            let user = await User.findOne({where: {
                    username: req.userData.username
                }})
            if(user.koins >= 200) {
                await Transaction.create({UserId: user.id, priceOfPurchase: 200, text: user.username + ' purchased a Kaverti username change for 200 koins', limited: false, ipId: Ip.createIfNotExists(req.ip, user)})
                await user.invalidateJWT()
                await user.updateUsername(req.body.username, req.body.password)
            } else {
                throw Errors.insufficientKoins
            }
            res.json({ success: true })
        } else if(
            req.body.twoFactorGetCode
        ) {
            let user = await User.findOne({where: {
                    username: req.userData.username
                }})
            throw Errors.featureDisabled
            function getTwoFactorAuthenticationCode() {
                const secretCode = speakeasy.generateSecret({
                    name: "Kaverti",
                });
                return {
                    otpauthUrl : secretCode.otpauth_url,
                    base32: secretCode.base32,
                };
            }
            res.status(200)
            res.json({code: getTwoFactorAuthenticationCode().base32, url: getTwoFactorAuthenticationCode().otpauthUrl})
        } else if(
            req.body.invalidateSession
        ) {
            let user = await User.findOne({where: {
                    username: req.userData.username
                }})
            if(user) {
                await user.invalidateJWT()
            } else {
                throw Errors.requestNotAuthorized
            }
        } else {
            res.json({ success: false })
        }
    } catch (e) { next(e) }
})

router.put('/experiments', auth, auth, async(req, res, next) => {
    try {
        if(!req.userData.username) {
            throw Errors.requestNotAuthorized
        }
        let queryObj = {
            attributes: {include: ['developerMode']},
            where: { username: req.userData.username }
        }
        let user = await User.findOne(queryObj)
        if(!user.developerMode) {
            throw Errors.deniedExperiments
        }
        if(req.body.theme !== undefined) {
            {
                let user = await User.update({theme: req.autosan.body.theme}, {
                where: {
                    username: req.userData.username
                }
            })

                res.json({success: true})
            }
        } else {
            res.json({ success: false })
        }
    } catch (e) { next(e) }
})

module.exports = router;
