# JSDoc: Source: schemas/user/emailChangeSchema.js

# Source: schemas/user/emailChangeSchema.js

    /*Canopy - The next generation of stoner streaming software
    Copyright (C) 2024-2025 Rainbownapkin and the TTN Community

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.*/

    //You could make an argument for making this part of the userModel
    //However, this is so rarely used the preformance benefits aren't worth the extra clutter

    //Config
    const config = require('../../../config.json');

    //Node Imports
    const crypto = require("node:crypto");

    //NPM Imports
    const {mongoose} = require('mongoose');

    //Local Imports
    const hashUtil = require('../../utils/hashUtils');
    const mailUtils = require('../../utils/mailUtils');

    /**
     * Email change token retention time
     */
    const daysToExpire = 7;

    /**
     * DB Schema for Document representing a single email change request
     */
    const emailChangeSchema = new mongoose.Schema({
        user: {
            type: mongoose.SchemaTypes.ObjectID,
            ref: "user",
            required: true
        },
        newEmail: {
            type: mongoose.SchemaTypes.String,
            required: true
        },
        token: {
            type: mongoose.SchemaTypes.String,
            required: true,
            //Use a cryptographically secure algorythm to create a random hex string from 16 bytes as our change/cancel token
            default: ()=>{return crypto.randomBytes(16).toString('hex')}
        },
        ipHash: {
            type: mongoose.SchemaTypes.String,
            required: true
        },
        date: {
            type: mongoose.SchemaTypes.Date,
            required: true,
            default: new Date()
        }
    });

    /**
     * Pre-Save function, ensures IP's are hashed and previous requests are deleted upon request creation
     */
    emailChangeSchema.pre('save', async function (next){
        //If we're saving an ip
        if(this.isModified('ipHash')){
            //Hash that sunnuvabitch
            this.ipHash = hashUtil.hashIP(this.ipHash);
        }

        if(this.isModified('user')){
            //Delete previous requests for the given user
            const requests = await this.model().deleteMany({user: this.user._id});
        }

        next();
    });

    /**
     * Schedulable function for processing expired email change requests
     */
    emailChangeSchema.statics.processExpiredRequests = async function(){
        //Pull all requests from the DB
        //Tested finding request by date, but mongoose kept throwing casting errors.
        //This seems to be an intermittent issue online. Maybe it will work in a future version?
        const requestDB = await this.find({});

        //Fire em all off at once without waiting for the last one to complete since we don't fuckin' need to
        for(let requestIndex in requestDB){
            //Pull request from requestDB by index
            const request = requestDB[requestIndex];

            //If the request hasn't been processed and it's been expired
            if(request.getDaysUntilExpiration() <= 0){
                //Delete the request
                await this.deleteOne({_id: request._id});
            }
        }
    }

    /**
     * Consumes email change token, changing email address on a given user
     */
    emailChangeSchema.methods.consume = async function(){
        //Populate the user reference
        await this.populate('user');

        const  oldMail = this.user.email;

        //Set the new email
        this.user.email = this.newEmail;

        //Save the user
        await this.user.save();

        //Delete the request token now that it has been consumed
        await this.deleteOne();

        //If we had a previous email address
        if(oldMail != null && oldMail != ''){
            //Notify it of the change
            await mailUtils.mailem(
                oldMail,
                `Email Change Notification - ${this.user.user}`,
                `<h1>Email Change Notification</h1>
                <p>The ${config.instanceName} account '${this.user.user}' is no longer associated with this email address.<br>
                <sup>If you received this email without request, you should <strong>immediately</strong> change your password and contact the server adminsitrator! -Tokebot</sup>`,
                true
            );
        }

        //Notify the new inbox of the change
        await mailUtils.mailem(
            this.newEmail,
            `Email Change Notification - ${this.user.user}`,
            `<h1>Email Change Notification</h1>
            <p>The ${config.instanceName} account '${this.user.user}' is now associated with this email address.<br>
            <sup>If you received this email without request, you should <strong>immediately</strong> check who's been inside your inbox! -Tokebot</sup>`,
            true
        );

    }

    /**
     * Generates email change URL from a given token
     * @returns {String} Email change URL generated from token
     */
    emailChangeSchema.methods.getChangeURL = function(){
        //Check for default port based on protocol
        if((config.protocol == 'http' && config.port == 80) || (config.protocol == 'https' && config.port == 443  || config.proxied)){
            //Return path
            return `${config.protocol}://${config.domain}/emailChange?token=${this.token}`;
        }else{
            //Return path
            return `${config.protocol}://${config.domain}:${config.port}/emailChange?token=${this.token}`;
        }
    }

    /**
     * Calculates days until token expiration
     * @returns {Number} Days until token expiration
     */
    emailChangeSchema.methods.getDaysUntilExpiration = function(){
            //Get request date
            const expirationDate = new Date(this.date);
            //Get expiration days and calculate expiration date
            expirationDate.setDate(expirationDate.getDate() + daysToExpire);
            //Calculate and return days until request expiration
            return ((expirationDate - new Date()) / (1000 * 60 * 60 * 24)).toFixed(1);
    }

    module.exports = mongoose.model("emailChange", emailChangeSchema);