const { ceil, floor } = require('lodash');
const moment = require('moment');
let winston = require('../config/winston');
const requestEvent = require('../event/requestEvent');
const messageEvent = require('../event/messageEvent');
const emailEvent = require('../event/emailEvent');

// NEW
// const PLANS_LIST = {
//     FREE_TRIAL: { requests: 3000,   messages: 0,    tokens: 250000,     email: 200,    chatbots: 20,       kbs: 50 }, // same as PREMIUM
//     SANDBOX:    { requests: 200,    messages: 0,    tokens: 100000,     email: 200,    chatbots: 2,        kbs: 50 },
//     BASIC:      { requests: 1000,   messages: 0,    tokens: 2000000,    email: 200,    chatbots: 10,       kbs: 200},
//     PREMIUM:    { requests: 3000,   messages: 0,    tokens: 5000000,    email: 200,    chatbots: 20,       kbs: 500},
//     CUSTOM:     { requests: 3000,   messages: 0,    tokens: 5000000,    email: 200,    chatbots: 20,       kbs: 500}
// }

const PLANS_LIST = {
    //FREE_TRIAL: { requests: 200,    messages: 0,    tokens: 100000,     voice_duration: 0,       email: 200,     chatbots: 20,      namespace: 3,   kbs: 50     }, // same as PREMIUM
    SANDBOX:    { requests: 200,    messages: 0,    tokens: 100000,     voice_duration: 0,          email: 200,     chatbots: 2,    namespace: 1,   kbs: 50 },
    BASIC:      { requests: 800,    messages: 0,    tokens: 2000000,    voice_duration: 0,          email: 200,     chatbots: 5,    namespace: 1,   kbs: 150 },
    PREMIUM:    { requests: 3000,   messages: 0,    tokens: 5000000,    voice_duration: 0,          email: 200,     chatbots: 20,   namespace: 3,   kbs: 300 },
    TEAM:       { requests: 5000,   messages: 0,    tokens: 10000000,   voice_duration: 0,          email: 200,     chatbots: 50,   namespace: 10,  kbs: 1000 },
    //CUSTOM:     { requests: 5000,   messages: 0,    tokens: 10000000,   voice_duration: 120000,  email: 200,     chatbots: 50,      namespace: 10,  kbs: 1000   },
    // FROM MARCH 2025
    FREE_TRIAL: { requests: 3000,   messages: 0,    tokens: 5000000,    voice_duration: 120000,     email: 200,     chatbots: 5,    namespace: 1,   kbs: 50 },  // same as PRO
    STARTER:    { requests: 800,    messages: 0,    tokens: 2000000,    voice_duration: 0,          email: 200,     chatbots: 5,    namespace: 1,   kbs: 150 },
    PRO:        { requests: 3000,   messages: 0,    tokens: 5000000,    voice_duration: 0,          email: 200,     chatbots: 20,   namespace: 3,   kbs: 300 },
    BUSINESS:   { requests: 5000,   messages: 0,    tokens: 10000000,   voice_duration: 0,          email: 200,     chatbots: 50,   namespace: 10,  kbs: 1000 },
    CUSTOM:     { requests: 5000,   messages: 0,    tokens: 10000000,   voice_duration: 120000,     email: 200,     chatbots: 50,   namespace: 10,  kbs: 1000 }
}



const typesList = ['requests', 'messages', 'email', 'tokens', 'voice_duration', 'chatbots', 'kbs']

let quotes_enabled = true;

class QuoteManager {

    constructor(config) {

        if (!config) {
            throw new Error('config is mandatory')
        }

        if (!config.tdCache) {
            throw new Error('config.tdCache is mandatory')
        }

        this.tdCache = config.tdCache;
        this.project;

    }

    // INCREMENT KEY SECTION - START
    async incrementRequestsCount(project, request) {

        this.project = project;
        let key = await this.generateKey(request, 'requests');
        winston.verbose("[QuoteManager] incrementRequestsCount key: " + key);

        await this.tdCache.incr(key)
        this.sendEmailIfQuotaExceeded(project, request, 'requests', key);
        return key;
    }

    async incrementMessagesCount(project, message) {

        this.project = project;
        let key = await this.generateKey(message, 'messages');
        winston.verbose("[QuoteManager] incrementMessagesCount key: " + key);

        await this.tdCache.incr(key)
        return key;
    }

    async incrementEmailCount(project, email) {

        this.project = project;
        let key = await this.generateKey(email, 'email');
        winston.verbose("[QuoteManager] incrementEmailCount key: " + key);

        await this.tdCache.incr(key)
        this.sendEmailIfQuotaExceeded(project, email, 'email', key);
        return key;
    }

    async incrementTokenCount(project, data) { // ?? cosa passo? il messaggio per vedere la data?

        this.project = project;
        let key = await this.generateKey(data, 'tokens');
        winston.verbose("[QuoteManager] incrementTokenCount key: " + key);

        if (quotes_enabled === false) {
            winston.debug("QUOTES DISABLED - incrementTokenCount")
            return key;
        }

        let tokens = data.tokens * data.multiplier;
        await this.tdCache.incrbyfloat(key, tokens);
        // await this.tdCache.incrby(key, tokens);
        this.sendEmailIfQuotaExceeded(project, data, 'tokens', key);
        return key;
    }

    async incrementVoiceDurationCount(project, request) {

        this.project = project;
        let key = await this.generateKey(request, 'voice_duration');
        winston.verbose("[QuoteManager] incrementVoiceDurationCount key: " + key);

        if (quotes_enabled === false) {
            winston.debug("QUOTES DISABLED - incrementVoiceDurationCount")
            return key;
        }

        if (request?.duration) {
            let duration = Math.round(request.duration / 1000); // from ms to s
            await this.tdCache.incrby(key, duration);

            this.sendEmailIfQuotaExceeded(project, request, 'voice_duration', key);
        }
    }
    // INCREMENT KEY SECTION - END


    async generateKey(object, type) {

        let objectDate = moment(object.createdAt);
        let subscriptionDate;

        if (this.project.isActiveSubscription === true) {
            if (this.project.profile.subStart) {
                subscriptionDate = moment(this.project.profile.subStart);
                winston.debug("Subscription date from subStart: " + subscriptionDate.toISOString());
            } else {
                // it should never happen
                winston.error("Error: quote manager - isActiveSubscription is true but subStart does not exists.")
            }
        } else {
            if (this.project.profile.subEnd) {
                subscriptionDate = moment(this.project.profile.subEnd);
                winston.debug("Subscription date from subEnd: " + subscriptionDate.toISOString());
            } else {
                subscriptionDate = moment(this.project.createdAt);
                winston.debug("Subscription date from project createdAt: " + subscriptionDate.toISOString());
            }
        }

        // Calculate the difference in months between the object date and the subscription date
        let diffInMonths = objectDate.diff(subscriptionDate, 'months');
        winston.debug("diffInMonths: ", diffInMonths)

        // Make a clone of the subscription date --> this operation could be avoided
        // Get the renewal date adding diffInMonths. Moment.js manage automatically the less longer month. 
        // E.g. if subscription date is 31 jan the renewals will be, 28/29 feb, 31 mar, 30 apr, etc.
        let renewalDate = subscriptionDate.clone().add(diffInMonths, 'months');
        // Force the renewal date equal to the last day of the month --> this operation could be avoided
        if (renewalDate.date() !== subscriptionDate.date()) {
            renewalDate = renewalDate.endOf('month');
        }
        winston.debug("renewalDate: ", renewalDate)

        return "quotes:" + type + ":" + this.project._id + ":" + renewalDate.format('M/D/YYYY');
        // return "quotes:" + type + ":" + this.project._id + ":" + renewalDate.format('MM/DD/YYYY');
        // return "quotes:" + type + ":" + this.project._id + ":" + renewalDate.toLocaleString();
    }

    // async _generateKey(object, type) {

    //     winston.debug("generateKey object ", object)
    //     winston.debug("generateKey type " + type)
    //     let subscriptionDate;

    //     if (this.project.isActiveSubscription === true) {
    //         if (this.project.profile.subStart) {
    //             subscriptionDate = this.project.profile.subStart;
    //         } else {
    //             // it should never happen
    //             winston.error("Error: quote manager - isActiveSubscription is true but subStart does not exists.")
    //         }
    //     } else {
    //         if (this.project.profile.subEnd) {
    //             subscriptionDate = this.project.profile.subEnd;
    //         } else {
    //             subscriptionDate = this.project.createdAt;
    //         }
    //     }

    //     let objectDate = object.createdAt;
    //     winston.debug("objectDate " + objectDate);

    //     // converts date in timestamps and transform from ms to s
    //     const objectDateTimestamp = ceil(objectDate.getTime() / 1000);
    //     const subscriptionDateTimestamp = ceil(subscriptionDate.getTime() / 1000);

    //     let ndays = (objectDateTimestamp - subscriptionDateTimestamp) / 86400;  // 86400 is the number of seconds in 1 day
    //     let nmonths = floor(ndays / 30); // number of month to add to the initial subscription date;

    //     let date = new Date(subscriptionDate);
    //     date.setMonth(date.getMonth() + nmonths);

    //     return "quotes:" + type + ":" + this.project._id + ":" + date.toLocaleDateString();
    // }

    /**
     * Get current quote for a single type (tokens or request or ...)
     */
    async getCurrentQuote(project, object, type) {
        this.project = project;
        let key = await this.generateKey(object, type);
        winston.verbose("[QuoteManager] getCurrentQuote key: " + key);

        let quote = await this.tdCache.get(key);
        return Number(quote);
    }

    /**
     * Get quotes for all types (tokens and request and ...)
     */
    async getAllQuotes(project, obj) {

        this.project = project;

        let quotes = {}
        for (let type of typesList) {

            let key = await this.generateKey(obj, type);
            let quote = await this.tdCache.get(key);

            quotes[type] = {
                quote: Number(quote)
            };
        }
        return quotes;
    }

    /**
     * Perform a check on a single type.
     * Returns TRUE if the limit is not reached --> operation can be performed
     * Returns FALSE if the limit is reached --> operation can't be performed
     */
    async checkQuote(project, object, type) {

        winston.verbose("checkQuote type " + type);
        if (quotes_enabled === false) {
            winston.verbose("QUOTES DISABLED - checkQuote for type " + type);
            return true;
        }

        this.project = project;
        let limits = await this.getPlanLimits();
        winston.verbose("limits for current plan: ", limits)

        let quote = await this.getCurrentQuote(project, object, type);
        winston.verbose("getCurrentQuote resp: " + quote)

        if (quote == null) {
            return true;
        }

        if (quote < limits[type]) {
            return true;
        } else {
            return false;
        }
    }

    async checkQuoteForAlert(project, object, type) {

        if (quotes_enabled === false) {
            winston.verbose("QUOTES DISABLED - checkQuote for type " + type);
            return (null, null);
        }

        this.project = project;
        let limits = await this.getPlanLimits();
        winston.verbose("limits for current plan: ", limits)

        let quote = await this.getCurrentQuote(project, object, type);
        winston.verbose("getCurrentQuote resp: ", quote)

        let data = {
            limits: limits,
            quote: quote
        }

        return data;
    }

    async sendEmailIfQuotaExceeded(project, object, type, key) {

        let data = await this.checkQuoteForAlert(project, object, type);
        let limits = data.limits;
        let limit = data.limits[type];
        let quote = data.quote;

        const checkpoint = await this.percentageCalculator(limit, quote);
        if (checkpoint == 0) {
            return;
        }
        winston.verbose("checkpoint perc: ", checkpoint);

        // Generate redis key
        let nKey = key + ":notify:" + checkpoint;
        let result = await this.tdCache.get(nKey);
        if (!result) {

            let allQuotes = await this.getAllQuotes(project, object);
            let quotes = await this.generateQuotesObject(allQuotes, limits);

            let data = {
                id_project: project._id,
                project_name: project.name,
                type: type,
                checkpoint: checkpoint,
                quotes: quotes
            }

            emailEvent.emit('email.send.quote.checkpoint', data);
            await this.tdCache.set(nKey, 'true', { EX: 2592000 }); //seconds in one month = 2592000
        } else {
            winston.verbose("Quota checkpoint reached email already sent.")
        }

    }

    async percentageCalculator(limit, quote) {

        let p = (quote / limit) * 100;

        if (p >= 100) { return 100; }
        if (p >= 95) { return 95; }
        if (p >= 75) { return 75; }
        if (p >= 50) { return 50; }

        return 0;

    }

    async invalidateCheckpointKeys(project, obj) {

        this.project = project;
        winston.verbose("invalidateCheckpointKeys project " + project._id);
        let requests_key = await this.generateKey(obj, 'requests');
        let tokens_key = await this.generateKey(obj, 'tokens');
        let email_key = await this.generateKey(obj, 'email');

        let checkpoints = ['50', '75', '95', '100']

        checkpoints.forEach(async (checkpoint) => {
            let nrequests_key = requests_key + ":notify:" + checkpoint;
            let ntokens_key = tokens_key + ":notify:" + checkpoint;
            let nemail_key = email_key + ":notify:" + checkpoint;

            winston.verbose("invalidateCheckpointKeys nrequests_key: " + nrequests_key);
            winston.verbose("invalidateCheckpointKeys ntokens_key: " + ntokens_key);
            winston.verbose("invalidateCheckpointKeys nemail_key: " + nemail_key);

            this.tdCache.del(nrequests_key);
            this.tdCache.del(ntokens_key);
            this.tdCache.del(nemail_key);

            return true;
        })

    }

    async generateQuotesObject(quotes, limits) {
        let quotes_obj = {
            requests: {
                quote: quotes.requests.quote,
                perc: ((quotes.requests.quote / limits['requests']) * 100).toFixed(1)
            },
            tokens: {
                quote: quotes.tokens.quote,
                perc: ((quotes.tokens.quote / limits['tokens']) * 100).toFixed(1)
            },
            email: {
                quote: quotes.email.quote,
                perc: ((quotes.email.quote / limits['email']) * 100).toFixed(1)
            }
        }
        return quotes_obj
    }


    async getPlanLimits(project) {

        if (project) {
            this.project = project
        };

        let limits;
        const plan = this.project.profile.name;

        if (this.project.profile.type === 'payment') {

            if (this.project.isActiveSubscription === false) {
                limits = PLANS_LIST.SANDBOX;
                return limits;

            }

            switch (plan) {
                case 'Starter':
                    limits = PLANS_LIST.STARTER
                    break;
                case 'Pro':
                    limits = PLANS_LIST.PRO
                    break;
                case 'Business':
                    limits = PLANS_LIST.BUSINESS
                    break;
                case 'Basic':
                    limits = PLANS_LIST.BASIC;
                    break;
                case 'Premium':
                    limits = PLANS_LIST.PREMIUM;
                    break;
                case 'Team':
                    limits = PLANS_LIST.TEAM;
                    break;
                case 'Custom':
                    limits = PLANS_LIST.CUSTOM;
                    break;
                case 'Growth':  // OLD PLAN
                    limits = PLANS_LIST.BASIC
                    break;
                case 'Scale':   // OLD PLAN
                    limits = PLANS_LIST.PREMIUM
                    break;
                case 'Plus':    // OLD PLAN
                    limits = PLANS_LIST.CUSTOM
                    break;
                default:
                    limits = PLANS_LIST.FREE_TRIAL;
            }

        } else {

            if (this.project.trialExpired === false) {
                limits = PLANS_LIST.FREE_TRIAL
            } else {
                limits = PLANS_LIST.SANDBOX;
            }

        }

        if (this.project?.profile?.quotes) {
            let profile_quotes = this.project?.profile?.quotes;
            const merged_quotes = Object.assign({}, limits, profile_quotes);
            winston.verbose("Custom Limits: ", limits)
            return merged_quotes;
        } else {
            winston.verbose("Default Limits: ", limits)
            return limits;
        }
    }

    async getCurrentSlot(project) {
        let subscriptionDate;
        if (project.isActiveSubscription === true) {
            if (project.profile.subStart) {
                subscriptionDate = moment(project.profile.subStart);
                winston.debug("Subscription date from subStart: " + subscriptionDate.toISOString());
            } else {
                // it should never happen
                winston.error("Error: quote manager - isActiveSubscription is true but subStart does not exists.")
            }
        } else {
            if (project.profile.subEnd) {
                subscriptionDate = moment(project.profile.subEnd);
                winston.debug("Subscription date from subEnd: " + subscriptionDate.toISOString());
            } else {
                subscriptionDate = moment(project.createdAt);
                winston.debug("Subscription date from project createdAt: " + subscriptionDate.toISOString());
            }
        }

        let now = moment();
        winston.debug("now: ", now);

        let diffInMonths = now.diff(subscriptionDate, 'months');
        winston.debug("diffInMonths: ", diffInMonths)

        let renewalDate = subscriptionDate.clone().add(diffInMonths, 'months').startOf('day');
        winston.debug("renewalDate: ", renewalDate)

        let slotEnd = subscriptionDate.clone().add(diffInMonths + 1, 'month');
        slotEnd.subtract(1, 'day').endOf('day')
        winston.debug("slotEnd: ", slotEnd)

        // let slot = {
        //     startDate: renewalDate.format('DD/MM/YYYY'),
        //     endDate: slotEnd.format('DD/MM/YYYY')
        // }
        let slot = {
            startDate: renewalDate,
            endDate: slotEnd
        }

        return slot;
    }



    start() {
        winston.verbose('QuoteManager start');

        if (process.env.QUOTES_ENABLED !== undefined) {
            if (process.env.QUOTES_ENABLED === false || process.env.QUOTES_ENABLED === 'false') {
                quotes_enabled = false;
            }
        }

        winston.info("QUOTES ENABLED: " + quotes_enabled);

        // TODO - Try to generalize to avoid repetition
        let incrementEventHandler = (object) => { }
        let checkEventHandler = (object) => { }


        // REQUESTS EVENTS - START
        // requestEvent.on('request.create.quote.before', async (payload) => {
        //     let result = await this.checkQuote(payload.project, payload.request, 'requests');
        //     if (result == true) {
        //         winston.info("Limit not reached - a request can be created")
        //     } else {
        //         winston.info("Requests limit reached for the current plan!")
        //     }
        //     return result;
        // });

        requestEvent.on('request.create.quote', async (payload) => {
            if (quotes_enabled === true) {
                winston.verbose("request.create.quote event catched");
                let result = await this.incrementRequestsCount(payload.project, payload.request);
                return result;
            } else {
                winston.verbose("QUOTES DISABLED - request.create.quote event")
            }
        })

        requestEvent.on('request.close.quote', async (payload) => {
            if (quotes_enabled === true) {
                winston.verbose("request.close.quote event catched");
                let result = await this.incrementVoiceDurationCount(payload.project, payload.request);
                return result;
            } else {
                winston.verbose("QUOTES DISABLED - request.close.quote event")
            }
        })
        // REQUESTS EVENTS - END


        // MESSAGES EVENTS - START
        // messageEvent.on('message.create.quote.before', async (payload) => {
        //     let result = await this.checkQuote(payload.project, payload.message, 'messages');
        //     if (result == true) {
        //         winston.info("Limit not reached - a message can be created")
        //     } else {
        //         winston.info("Messages limit reached for the current plan!")
        //     }
        //     return result;
        // })

        messageEvent.on('message.create.quote', async (payload) => {
            if (quotes_enabled === true) {
                winston.verbose("message.create.quote event catched");
                let result = await this.incrementMessagesCount(payload.project, payload.message);
                return result;
            } else {
                winston.verbose("QUOTES DISABLED - message.create.quote event")
            }
        })
        // MESSAGES EVENTS - END


        // EMAIL EVENTS - START - Warning! Can't be used for check quote
        // emailEvent.on('email.send.before', async (payload) => {
        //     let result = await this.checkQuote(payload.project, payload.email, 'email');
        //     if (result == true) {
        //         winston.info("Limit not reached - a message can be created")
        //     } else {
        //         winston.info("Email limit reached for the current plan!")
        //     }
        //     return result;
        // })

        emailEvent.on('email.send.quote', async (payload) => {
            if (quotes_enabled === true) {
                winston.verbose("email.send event catched");
                let result = await this.incrementEmailCount(payload.project, payload.email);
                return result;
            } else {
                winston.verbose("QUOTES DISABLED - email.send event")
            }
        })
        // EMAIL EVENTS - END
    }


}

module.exports = { QuoteManager };