/* jslint node: true */
'use strict';

const sysDb     = require('./database.js').dbs.system;
const {
    getISOTimestampString
}               = require('./database.js');
const Errors    = require('./enig_error.js');

//  deps
const _         = require('lodash');
const moment    = require('moment');

/*
    System Event Log & Stats
    ------------------------

    System & user specific:
    * Events for generating various statistics, logs such as last callers, etc.
    * Stats such as counters

    User specific stats are simply an alternate interface to user properties, while
    system wide entries are handled on their own. Both are read accessible non-blocking
    making them easily available for MCI codes for example.
*/
class StatLog {
    constructor() {
        this.systemStats = {};
    }

    init(cb) {
        //
        //  Load previous state/values of |this.systemStats|
        //
        const self = this;

        sysDb.each(
            `SELECT stat_name, stat_value
            FROM system_stat;`,
            (err, row) => {
                if(row) {
                    self.systemStats[row.stat_name] = row.stat_value;
                }
            },
            err => {
                return cb(err);
            }
        );
    }

    get KeepDays() {
        return {
            Forever : -1,
        };
    }

    get KeepType() {
        return {
            Forever : 'forever',
            Days    : 'days',
            Max     : 'max',
            Count   : 'max',
        };
    }

    get Order() {
        return {
            Timestamp       : 'timestamp_asc',
            TimestampAsc    : 'timestamp_asc',
            TimestampDesc   : 'timestamp_desc',
            Random          : 'random',
        };
    }

    setNonPersistentSystemStat(statName, statValue) {
        this.systemStats[statName] = statValue;
    }

    incrementNonPersistentSystemStat(statName, incrementBy) {
        incrementBy = incrementBy || 1;

        let newValue = parseInt(this.systemStats[statName]);
        if(!isNaN(newValue)) {
            newValue += incrementBy;
        } else {
            newValue = incrementBy;
        }
        this.setNonPersistentSystemStat(statName, newValue);
        return newValue;
    }

    setSystemStat(statName, statValue, cb) {
        //  live stats
        this.systemStats[statName] = statValue;

        //  persisted stats
        sysDb.run(
            `REPLACE INTO system_stat (stat_name, stat_value)
            VALUES (?, ?);`,
            [ statName, statValue ],
            err => {
                //  cb optional - callers may fire & forget
                if(cb) {
                    return cb(err);
                }
            }
        );
    }

    getSystemStat(statName) { return this.systemStats[statName]; }

    getSystemStatNum(statName) {
        return parseInt(this.getSystemStat(statName)) || 0;
    }

    incrementSystemStat(statName, incrementBy, cb) {
        const newValue = this.incrementNonPersistentSystemStat(statName, incrementBy);
        return this.setSystemStat(statName, newValue, cb);
    }

    //
    //  User specific stats
    //  These are simply convenience methods to the user's properties
    //
    setUserStatWithOptions(user, statName, statValue, options, cb) {
        //  note: cb is optional in PersistUserProperty
        user.persistProperty(statName, statValue, cb);

        if(!options.noEvent) {
            const Events = require('./events.js');  //  we need to late load currently
            Events.emit(Events.getSystemEvents().UserStatSet, { user, statName, statValue } );
        }
    }

    setUserStat(user, statName, statValue, cb) {
        return this.setUserStatWithOptions(user, statName, statValue, {}, cb);
    }

    getUserStat(user, statName) {
        return user.properties[statName];
    }

    getUserStatNum(user, statName) {
        return parseInt(this.getUserStat(user, statName)) || 0;
    }

    incrementUserStat(user, statName, incrementBy, cb) {
        incrementBy = incrementBy || 1;

        const oldValue = user.getPropertyAsNumber(statName) || 0;
        const newValue = oldValue + incrementBy;

        this.setUserStatWithOptions(
            user,
            statName,
            newValue,
            { noEvent : true },
            err => {
                if(!err) {
                    const Events = require('./events.js');  //  we need to late load currently
                    Events.emit(
                        Events.getSystemEvents().UserStatIncrement,
                        {
                            user,
                            statName,
                            oldValue,
                            statIncrementBy : incrementBy,
                            statValue       : newValue
                        }
                    );
                }

                if(cb) {
                    return cb(err);
                }
            }
        );
    }

    //  the time "now" in the ISO format we use and love :)
    get now() {
        return getISOTimestampString();
    }

    appendSystemLogEntry(logName, logValue, keep, keepType, cb) {
        sysDb.run(
            `INSERT INTO system_event_log (timestamp, log_name, log_value)
            VALUES (?, ?, ?);`,
            [ this.now, logName, logValue ],
            () => {
                //
                //  Handle keep
                //
                if(-1 === keep) {
                    if(cb) {
                        return cb(null);
                    }
                    return;
                }

                switch(keepType) {
                    //  keep # of days
                    case 'days' :
                        sysDb.run(
                            `DELETE FROM system_event_log
                            WHERE log_name = ? AND timestamp <= DATETIME("now", "-${keep} day");`,
                            [ logName ],
                            err => {
                                //  cb optional - callers may fire & forget
                                if(cb) {
                                    return cb(err);
                                }
                            }
                        );
                        break;

                    case 'count':
                    case 'max' :
                        //  keep max of N/count
                        sysDb.run(
                            `DELETE FROM system_event_log
                            WHERE id IN(
                                SELECT id 
                                FROM system_event_log
                                WHERE log_name = ?
                                ORDER BY id DESC
                                LIMIT -1 OFFSET ${keep}
                            );`,
                            [ logName ],
                            err => {
                                if(cb) {
                                    return cb(err);
                                }
                            }
                        );
                        break;

                    case 'forever' :
                    default :
                        //  nop
                        break;
                }
            }
        );
    }

    /*
        Find System Log entries by |filter|:

        filter.logName (required)
        filter.resultType = (obj) | count
            where obj contains timestamp and log_value
        filter.limit
        filter.date - exact date to filter against
        filter.order = (timestamp) | timestamp_asc | timestamp_desc | random
    */
    findSystemLogEntries(filter, cb) {
        filter = filter || {};
        if(!_.isString(filter.logName)) {
            return cb(Errors.MissingParam('filter.logName is required'));
        }

        filter.resultType   = filter.resultType || 'obj';
        filter.order        = filter.order || 'timestamp';

        let sql;
        if('count' === filter.resultType) {
            sql =
                `SELECT COUNT() AS count
                FROM system_event_log`;
        } else {
            sql =
                `SELECT timestamp, log_value
                FROM system_event_log`;
        }

        sql += ' WHERE log_name = ?';

        if(filter.date) {
            filter.date = moment(filter.date);
            sql += ` AND DATE(timestamp, "localtime") = DATE("${filter.date.format('YYYY-MM-DD')}")`;
        }

        if('count' !== filter.resultType) {
            switch(filter.order) {
                case 'timestamp' :
                case 'timestamp_asc' :
                    sql += ' ORDER BY timestamp ASC';
                    break;

                case 'timestamp_desc' :
                    sql += ' ORDER BY timestamp DESC';
                    break;

                case 'random'   :
                    sql += ' ORDER BY RANDOM()';
                    break;
            }
        }

        if(_.isNumber(filter.limit) && 0 !== filter.limit) {
            sql += ` LIMIT ${filter.limit}`;
        }

        sql += ';';

        if('count' === filter.resultType) {
            sysDb.get(sql, [ filter.logName ], (err, row) => {
                return cb(err, row ? row.count : 0);
            });
        } else {
            sysDb.all(sql, [ filter.logName ], (err, rows) => {
                return cb(err, rows);
            });
        }
    }

    getSystemLogEntries(logName, order, limit, cb) {
        if(!cb && _.isFunction(limit)) {
            cb      = limit;
            limit   = 0;
        } else {
            limit = limit || 0;
        }

        const filter = {
            logName,
            order,
            limit,
        };
        return this.findSystemLogEntries(filter, cb);
    }

    appendUserLogEntry(user, logName, logValue, keepDays, cb) {
        sysDb.run(
            `INSERT INTO user_event_log (timestamp, user_id, session_id, log_name, log_value)
            VALUES (?, ?, ?, ?, ?);`,
            [ this.now, user.userId, user.sessionId, logName, logValue ],
            err => {
                if(err) {
                    if(cb) {
                        cb(err);
                    }
                    return;
                }
                //
                //  Handle keepDays
                //
                if(-1 === keepDays) {
                    if(cb) {
                        return cb(null);
                    }
                    return;
                }

                sysDb.run(
                    `DELETE FROM user_event_log
                    WHERE user_id = ? AND log_name = ? AND timestamp <= DATETIME("now", "-${keepDays} day");`,
                    [ user.userId, logName ],
                    err => {
                        //  cb optional - callers may fire & forget
                        if(cb) {
                            return cb(err);
                        }
                    }
                );
            }
        );
    }

    initUserEvents(cb) {
        const systemEventUserLogInit = require('./sys_event_user_log.js');
        systemEventUserLogInit(this);
        return cb(null);
    }
}

module.exports = new StatLog();
