let usage = {},
    common = require('./../../utils/common.js'),
    logger = require('../../logger'),
    mmdbreader = require('maxmind-db-reader'),
    moment = require('moment'),
    time = require('time')(Date),
    semusiConfig = require('./../../config'),
    dataProducer = require('./../../utils/common.data.producer.js'),
    AWS = require('aws-sdk'),
    request = require('request'),
    psqlUtils = require('./../../utils/pgsql.utils'),
    ip2loc = require("ip2location-nodejs");
    let errorHandler = require('./../../utils/error.handler.js');

    let geoReader = mmdbreader.openSync(__dirname + '/../../utils/geoip/GeoIP2-City.mmdb');
    ip2loc.IP2Location_init(__dirname + '/../../utils/IP2LOCATION-LITE-DB11/IP2LOCATION-LITE-DB11.BIN');

    let postback = require('./../../ref_plugins/postback_url.js'), fs = require('fs');
    let mail = require('./../mgmt/mail');

(function (usage) {

    // Performs geoip lookup for the IP address of the app user
    usage.beginUserSession = function (params) {
        populateParamsWithLocationData(params);
        common.db.collection('app_users' + params.app_id).findOne({'_id': params.app_user_id }, function (err, dbAppUser){
            processUserSession(dbAppUser, params);
        });
    };

    /*usage.endUserSession = function (params){
        common.db.collection('app_users' + params.app_id).findOne({'_id': params.app_user_id},{sessions:{$slice:-1}}, function (err, dbAppUser){
            if (!dbAppUser || err) {
                return true;
            }
            let updateSessions = {};
            let sessionDuration = parseInt(params.qstring.session_duration);
            let totalSessionDuration = (dbAppUser.tsd)? dbAppUser.tsd :0;
            totalSessionDuration+=sessionDuration;
            let session = (dbAppUser.sessions && dbAppUser.sessions.length>0)?dbAppUser.sessions[0]:{};
            session.et = params.time.timestamp;
            session.sd = sessionDuration;

            common.fillTimeObject(params, updateSessions, common.dbMap['duration'], sessionDuration);

            common.db.collection('sessions').update({'_id': params.app_id}, {'$inc': updateSessions}, {'upsert': false});

            common.db.collection('app_users' + params.app_id).update({'_id': params.app_user_id,sessions: { $elemMatch: { st: session.st }}}, {'$set': {hos:false,sd:0,tsd:totalSessionDuration,"sessions.$.sd" : session.sd}}, function() {
                return true;
            });

        });
    };*/

    usage.endUserSession = function (params) {
        // As soon as we receive the end_session we set the timestamp
        // This timestamp is used inside processUserSession
        let userProps = {};
        userProps[common.dbUserMap['last_end_session_timestamp']] = params.time.nowWithoutTimestamp.unix();
        userProps['updatedAt'] = common.getCurrentEpochTime();

        common.db.collection('app_users' + params.app_id).update({'_id': params.app_user_id}, {'$set': userProps}, function() {});

        setTimeout(function() {
            common.db.collection('app_users' + params.app_id).findOne({'_id': params.app_user_id }, function (err, dbAppUser){
                if (!dbAppUser || err) {
                    return true;
                }

                let lastBeginSession = dbAppUser[common.dbUserMap['last_begin_session_timestamp']],
                    currDateWithoutTimestamp = new Date();

                // We can't use the params.time.timestamp since we are inside a setTimeout
                // and we need the actual timestamp
                currDateWithoutTimestamp.setTimezone(params.appTimezone);
                let currTimestamp = Math.round(currDateWithoutTimestamp.getTime() / 1000);


                // If ongoing session flag is set and there is a 11 second difference between the current
                // timestamp and the timestamp when the last begin_session received then remove the flag
                // to let the next end_session complete the session
                if (dbAppUser[common.dbUserMap['has_ongoing_session']] && (currTimestamp - lastBeginSession) > 11) {
                    let userProps = {};
                    userProps[common.dbUserMap['has_ongoing_session']] = 1;

                    common.db.collection('app_users' + params.app_id).update({'_id': params.app_user_id}, {'$unset': userProps}, function() {
                        endSession(true);
                    });
                } else {
                    endSession();
                }

                function endSession(overrideFlag) {
                    // If user does not have an ongoing session end it
                    // Ongoing session flag is set inside processUserSession
                    if (overrideFlag || !dbAppUser[common.dbUserMap['has_ongoing_session']]) {

                        // If the user does not exist in the app_users collection or she does not have any
                        // previous session duration stored than we dont need to calculate the session
                        // duration range for this user.
                        if (dbAppUser[common.dbUserMap['session_duration']]) {
                            processSessionDurationRange(dbAppUser[common.dbUserMap['session_duration']], params);
                        }
                    }
                }
            });
        }, 10000);

    };

    usage.processSessionDuration = function (params, callback) {

        let updateSessions = {},
            session_duration = parseInt(params.qstring.session_duration);

        if (session_duration == (session_duration | 0)) {
            /*if (common.config.api.session_duration_limit && session_duration > common.config.api.session_duration_limit) {
                session_duration = common.config.api.session_duration_limit;
            }*/

            common.fillTimeObject(params, updateSessions, common.dbMap['duration'], session_duration);

            common.db.collection('sessions').update({'_id': params.app_id}, {'$inc': updateSessions}, {'upsert': false});

            // sd: session duration, tsd: total session duration. common.dbUserMap is not used here for readability purposes.
            let updatedAt = common.getCurrentEpochTime();
            common.db.collection('app_users' + params.app_id).update({'_id': params.app_user_id}, {'$inc': {'sd': session_duration, 'tsd': session_duration}, '$set':{'updatedAt':updatedAt}},  function() {
                if (callback) {
                    callback();
                }
            });
        }
    };

    function processSessionDurationRange(totalSessionDuration, params) {
        let durationRanges = [
                [0,10],
                [11,30],
                [31,60],
                [61,180],
                [181,600],
                [601,1800],
                [1801,3600]
            ],
            durationMax = 3601,
            calculatedDurationRange,
            updateSessions = {};

        if (totalSessionDuration >= durationMax) {
            calculatedDurationRange = (durationRanges.length) + '';
        } else {
            for (let i=0; i < durationRanges.length; i++) {
                if (totalSessionDuration <= durationRanges[i][1] && totalSessionDuration >= durationRanges[i][0]) {
                    calculatedDurationRange = i + '';
                    break;
                }
            }
        }

        common.fillTimeObject(params, updateSessions, common.dbMap['durations'] + '.' + calculatedDurationRange);
        common.db.collection('sessions').update({'_id': params.app_id}, {'$inc': updateSessions, '$addToSet': {'meta.d-ranges': calculatedDurationRange}}, {'upsert': false});

        // sd: session duration. common.dbUserMap is not used here for readability purposes.
        let updatedAt = common.getCurrentEpochTime();
        common.db.collection('app_users' + params.app_id).update({'_id': params.app_user_id}, {'$set': {'sd': 0, 'updatedAt':updatedAt}}, {'upsert': true});
    }

    function processUserSession(dbAppUser, params) {
        let updateSessions = {},
            updateUsers = {},
            updateLocations = {},
            updateCities = {},
            userRanges = {},
            loyaltyRanges = [
                [0,1],
                [2,2],
                [3,5],
                [6,9],
                [10,19],
                [20,49],
                [50,99],
                [100,499]
            ],
            sessionFrequency = [
                [0,1],
                [1,24],
                [24,48],
                [48,72],
                [72,96],
                [96,120],
                [120,144],
                [144,168],
                [168,192],
                [192,360],
                [360,744]
            ],
            sessionFrequencyMax = 744,
            calculatedFrequency,
            loyaltyMax = 500,
            calculatedLoyaltyRange,
            uniqueLevels = [],
            isNewUser = false;

        common.fillTimeObject(params, updateSessions, common.dbMap['total']);
        common.fillTimeObject(params, updateLocations, params.user.country + '.' + common.dbMap['total']);

        if (common.config.api.city_data !== false) {
            common.fillTimeObject(params, updateCities, params.user.city + '.' + common.dbMap['total']);
        }

        if (dbAppUser && dbAppUser[common.dbUserMap['first_seen']]) {
            let userLastSeenTimestamp = dbAppUser[common.dbUserMap['last_seen']],
                currDate = common.getDate(params.time.timestamp, params.appTimezone),
                userLastSeenDate = common.getDate(userLastSeenTimestamp, params.appTimezone),
                secInMin = (60 * (currDate.getMinutes())) + currDate.getSeconds(),
                secInHour = (60 * 60 * (currDate.getHours())) + secInMin,
                secInMonth = (60 * 60 * 24 * (currDate.getDate() - 1)) + secInHour,
                secInYear = (60 * 60 * 24 * (common.getDOY(params.time.timestamp, params.appTimezone) - 1)) + secInHour;

            // If the last end_session is received less than 15 seconds ago we will ignore
            // current begin_session request and mark this user as having an ongoing session
            let lastEndSession = dbAppUser[common.dbUserMap['last_end_session_timestamp']];

            if (lastEndSession && (params.time.nowWithoutTimestamp.unix() - lastEndSession) < 15) {
                let userProps = {};
                userProps[common.dbUserMap['has_ongoing_session']] = true;
                userProps[common.dbUserMap['last_begin_session_timestamp']] = params.time.nowWithoutTimestamp.unix();
                userProps['updatedAt'] = common.getCurrentEpochTime();

                common.db.collection('app_users' + params.app_id).update({'_id': params.app_user_id}, {'$set': userProps},{'upsert':true}, function() {});

                return true;
            }

            // Calculate the frequency range of the user

            if ((params.time.timestamp - userLastSeenTimestamp) >= (sessionFrequencyMax * 60 * 60)) {
                calculatedFrequency = sessionFrequency.length + '';
            } else {
                for (let i=0; i < sessionFrequency.length; i++) {
                    if ((params.time.timestamp - userLastSeenTimestamp) < (sessionFrequency[i][1] * 60 * 60) &&
                        (params.time.timestamp - userLastSeenTimestamp) >= (sessionFrequency[i][0] * 60 * 60)) {
                        calculatedFrequency = i + '';
                        break;
                    }
                }
            }

            // Calculate the loyalty range of the user

            let userSessionCount = dbAppUser[common.dbUserMap['session_count']] + 1;

            if (userSessionCount >= loyaltyMax) {
                calculatedLoyaltyRange = loyaltyRanges.length + '';
            } else {
                for (let i = 0; i < loyaltyRanges.length; i++) {
                    if (userSessionCount <= loyaltyRanges[i][1] && userSessionCount >= loyaltyRanges[i][0]) {
                        calculatedLoyaltyRange = i + '';
                        break;
                    }
                }
            }

            if (userLastSeenDate.getFullYear() == params.time.yearly &&
                Math.ceil(common.moment(userLastSeenDate).format("DDD") / 7) < params.time.weekly) {
                uniqueLevels[uniqueLevels.length] = params.time.yearly + ".w" + params.time.weekly;
            }

            if (userLastSeenTimestamp < (params.time.timestamp - secInMin)) {
                // We don't need to put hourly fragment to the unique levels array since
                // we will store hourly data only in sessions collection
                updateSessions[params.time.hourly + '.' + common.dbMap['unique']] = 1;
            }

            if (userLastSeenTimestamp < (params.time.timestamp - secInHour)) {
                uniqueLevels[uniqueLevels.length] = params.time.daily;
            }

            if (userLastSeenTimestamp < (params.time.timestamp - secInMonth)) {
                uniqueLevels[uniqueLevels.length] = params.time.monthly;
            }

            if (userLastSeenTimestamp < (params.time.timestamp - secInYear)) {
                uniqueLevels[uniqueLevels.length] = params.time.yearly;
            }

            for (let i = 0; i < uniqueLevels.length; i++) {
                updateSessions[uniqueLevels[i] + '.' + common.dbMap['unique']] = 1;
                updateLocations[uniqueLevels[i] + '.' + params.user.country + '.' + common.dbMap['unique']] = 1;
                updateUsers[uniqueLevels[i] + '.' + common.dbMap['frequency'] + '.' + calculatedFrequency] = 1;
                updateUsers[uniqueLevels[i] + '.' + common.dbMap['loyalty'] + '.' + calculatedLoyaltyRange] = 1;

                if (common.config.api.city_data !== false) {
                    updateCities[uniqueLevels[i] + '.' + params.user.city + '.' + common.dbMap['unique']] = 1;
                }
            }

            if (uniqueLevels.length != 0) {
                userRanges['meta.' + 'f-ranges'] = calculatedFrequency;
                userRanges['meta.' + 'l-ranges'] = calculatedLoyaltyRange;
                common.db.collection('users').update({'_id': params.app_id}, {'$inc': updateUsers, '$addToSet': userRanges}, {'upsert': true});
            }
        } else {
            isNewUser = true;

            // User is not found in app_users collection so this means she is both a new and unique user.
            common.fillTimeObject(params, updateSessions, common.dbMap['new']);
            common.fillTimeObject(params, updateSessions, common.dbMap['unique']);
            common.fillTimeObject(params, updateLocations, params.user.country + '.' + common.dbMap['new']);
            common.fillTimeObject(params, updateLocations, params.user.country + '.' + common.dbMap['unique']);

            if (common.config.api.city_data !== false) {
                common.fillTimeObject(params, updateCities, params.user.city + '.' + common.dbMap['new']);
                common.fillTimeObject(params, updateCities, params.user.city + '.' + common.dbMap['unique']);
            }

            // First time user.
            calculatedLoyaltyRange = '0';
            calculatedFrequency = '0';

            common.fillTimeObject(params, updateUsers, common.dbMap['frequency'] + '.' + calculatedFrequency);
            userRanges['meta.' + 'f-ranges'] = calculatedFrequency;

            common.fillTimeObject(params, updateUsers, common.dbMap['loyalty'] + '.' + calculatedLoyaltyRange);
            userRanges['meta.' + 'l-ranges'] = calculatedLoyaltyRange;

            common.db.collection('users').update({'_id': params.app_id}, {'$inc': updateUsers, '$addToSet': userRanges}, {'upsert': true});
        }
         
        common.db.collection('sessions').update({'_id': params.app_id}, {'$inc': updateSessions}, {'upsert': true});
        common.db.collection('locations').update({'_id': params.app_id}, {'$inc': updateLocations, '$addToSet': {'meta.countries': params.user.country}}, {'upsert': true});

        if (common.config.api.city_data !== false && params.app_cc == params.user.country) {
            common.db.collection('cities').update({'_id': params.app_id}, {'$inc': updateCities, '$set': {'country': params.user.country}, '$addToSet': {'meta.cities': params.user.city}}, {'upsert': true});
        }

        processMetrics(dbAppUser, uniqueLevels, params);
    }

    function processMetrics(user, uniqueLevels, params) {

        let userProps = {},
            isNewUser = (user && user[common.dbUserMap['first_seen']])? false : true;

        if (isNewUser) {
            //Check if timestamp is less than the current time stamp. Then use current time stamp.
            if(params.time.timestamp<common.getCurrentEpochTime()){
                params.time.timestamp = common.getCurrentEpochTime();
            }
            userProps[common.dbUserMap['first_seen']] = params.time.timestamp;
            userProps[common.dbUserMap['last_seen']] = params.time.timestamp;
            userProps[common.dbUserMap['device_id']] = params.qstring.device_id;
            userProps[common.dbUserMap['country_code']] = params.user.cc;
            userProps[common.dbUserMap['city']] = params.user.city;
            userProps[common.dbUserMap['region']] = params.user.region;
            userProps[common.dbUserMap['country']] = params.user.country;

            if(params.qstring.sdk_version){
                userProps[common.dbUserMap['sdk_version']] = params.qstring.sdk_version;
            }
            if(params.qstring.android_id){
                userProps['android_id'] = params.qstring.android_id;
            }
            if(params.qstring._appsFlyerId){
                userProps['appsflyer_device_id'] = params.qstring._appsFlyerId;
            }

        } else {
            if (parseInt(user[common.dbUserMap['last_seen']], 10) < params.time.timestamp) {
                userProps[common.dbUserMap['last_seen']] = params.time.timestamp;
            }

            if (user[common.dbUserMap['city']] != params.user.city) {
                userProps[common.dbUserMap['city']] = params.user.city;
            }

            if (user[common.dbUserMap['country_code']] != params.user.cc) {
                userProps[common.dbUserMap['country_code']] = params.user.cc;
            }

            if (user[common.dbUserMap['country']] != params.user.country) {
                userProps[common.dbUserMap['country']] = params.user.country;
            }

            if (user[common.dbUserMap['region']] != params.user.region) {
                userProps[common.dbUserMap['region']] = params.user.region;
            }

            if (user[common.dbUserMap['device_id']] != params.qstring.device_id) {
                userProps[common.dbUserMap['device_id']] = params.qstring.device_id;
            }
            if(user[common.dbUserMap['sdk_version']] != params.qstring.sdk_version){
                userProps[common.dbUserMap['sdk_version']] = params.qstring.sdk_version;
            }
            if(user['android_id'] != params.qstring.android_id){
                userProps['android_id'] = params.qstring.android_id;
            }
            if(params.qstring._appsFlyerId){
                userProps['appsflyer_device_id'] = params.qstring._appsFlyerId;
            }
        }

        if (!params.qstring.metrics) {
            userProps['updatedAt'] = common.getCurrentEpochTime();
            // sc: session count. common.dbUserMap is not used here for readability purposes.
            common.db.collection('app_users' + params.app_id).update({'_id':params.app_user_id}, {'$inc':{'sc':1}, '$set':userProps}, {'upsert':true}, function () {
                updateTimelyUserSession(params);
            });
            return false;
        }
        else
        {
               let userfields = [];
               let refObj = {};
               //for each metrics add it to the user's device profile
                //check only for alias and the _custom appended properties
                for(let attributeName in params.qstring.metrics)
                {
                    if(attributeName == '_alias')
                    {
                        //Check if alias is being set or updated.
                        if(isNewUser==false && (user.alias === undefined || user.alias !== params.qstring.metrics[attributeName])){
                            registerDeviceForProcessing(params.app_id,params.qstring.device_id);
                        }
                        userProps['alias']  =  params.qstring.metrics[attributeName];
                    }
                    else if(attributeName.indexOf('_custom') > -1)
                    {
                        userProps[attributeName] = params.qstring.metrics[attributeName];
                        //Store the custom variables with type, so that Audience segment can handle them appropriately while querying.
                        let customlet = {};
                        customVar.name = attributeName;
                        customVar.type = typeof(params.qstring.metrics[attributeName]);
                        userfields.push(customVar);
                    }

                    if(attributeName == '_ref')
                    {
                        let tmpIUObj = {};
                        //Extract refname from utm_source
                        common.populateHistoryObject(params.qstring.metrics[attributeName],refObj);
                        refObj.installer = (params.qstring.metrics['_installer']) ? common.toFirstUpper(params.qstring.metrics['_installer']):"";


                        if(isNewUser)
                        {

                            refObj.type = "I";
                            refObj.dtEntry = params.time.timestamp;
                            refObj.ip_address = params.ip_address;
                            refObj.isnew = true;


                            //record uninstall in the total object
                            common.fillTimeObject(params,tmpIUObj,"T",1);
                            common.fillTimeObject(params,tmpIUObj,"I",1);
                            common.db.collection("install_counter").update({'_id':  params.app_id}, {'$inc': tmpIUObj}, {'upsert': true, 'safe': true}, function(err, result) {
                            });

                            common.db.collection('app_users' + params.app_id).update({'_id':params.app_user_id}, {'$addToSet':{'history':refObj}},{'upsert': true, 'safe': true}, function () {

                            });

                            //Hit callback url.
                            // if((params.callbackurl!==undefined && params.callbackurl!=="") || (refObj.trackId!== undefined && refObj.trackId!=="")){
                            //     if(params.qstring.metrics[attributeName]!=="self"){
                            //         sendCallBack(params.callbackurl+params.qstring.metrics[attributeName],refObj.trackId,params.app_id,params.qstring.device_id);
                            //     }
                            // }
                            //Register new device to queue so that ML can process its gender and interests.
                            registerDeviceForProcessing(params.app_id,params.qstring.device_id);
                        }
                        else
                        {
                            //check if the last entry was for an uninstall
                            //db.app_users54f69ad165d7b8ca05000001.find({"did":"123456","history.type":"U"},{"history":{$slice:-1}})
                            common.db.collection('app_users' + params.app_id).find({'_id':params.app_user_id},{"history":{"$slice":-1}}).toArray(function(err,deviceinfo)
                            {
                                if(deviceinfo && deviceinfo.length >0 && deviceinfo[0] !== undefined && deviceinfo[0].history !== undefined)
                                {
                                    if(deviceinfo[0].history[0].type == "U")
                                    {
                                        refObj.type = "I";
                                        refObj.dtEntry = params.time.timestamp;
                                        refObj.ip_address = params.ip_address;
                                        refObj.isnew = false;
                                        common.db.collection('app_users' + params.app_id).update({'_id':params.app_user_id}, {'$addToSet':{'history':refObj}}, {'upsert': true, 'safe': true}, function () {});
                                    }
                                    else if(deviceinfo[0].history[0].type == "I"){
                                        //For an existing users who has last history type as "I", check if the referal is same otherwise update it.
                                        if(deviceinfo[0].history[0].refname!=refObj.refname){
                                            //popout the old entry and push the new entry.
                                            common.db.collection('app_users' + params.app_id).update({'_id':params.app_user_id}, {'$pop':{'history':1}}, function (err,poppedUser) {
                                                //Push the updated entry.
                                                refObj.type="I",
                                                refObj.dtEntry = deviceinfo[0].history[0].dtEntry; //Keep the original time.
                                                refObj.ip_address = deviceinfo[0].history[0].ip_address; //Keep the original ip address.
                                                refObj.isnew = deviceinfo[0].history[0].isnew;
                                                common.db.collection('app_users' + params.app_id).update({'_id':params.app_user_id}, {'$addToSet':{'history':refObj}}, function (err,pushedUser) {
                                                    logger.info('Updated user attribution.');
                                                });
                                            });
                                        }
                                    }
                                }

                            });
                        }

                    }
                }

                userProps['updatedAt'] = common.getCurrentEpochTime();
                common.db.collection('app_users' + params.app_id).update({'_id':params.app_user_id}, {'$set':userProps}, {'upsert':true}, function () {});


                //also set the fields to the custom field names, so developers can later quickly find out which custom
                //fields has been sent by the devices so far
                //db.app_customfields546dea506dd9ec7404000007.update({},{ $addToSet: { fields: { $each: [ 'name','age','gender' ] } } },{'upsert':true})
                if(userfields.length>0){
                    common.db.collection('app_customfields').update({"_id":params.app_id},{'$addToSet':{ 'fields': { '$each': userfields } }}, {'upsert':true}, function () {});
                }


        }

        let predefinedMetrics = [
            { db: "devices", metrics: [{ name: "_device", set: "devices", short_code: common.dbUserMap['device'] }] },
            { db: "carriers", metrics: [{ name: "_carrier", set: "carriers", short_code: common.dbUserMap['carrier'] }] },
            { db: "device_details", metrics: [{ name: "_os", set: "os", short_code: common.dbUserMap['platform'] }, { name: "_os_version", set: "os_versions", short_code: common.dbUserMap['platform_version'] }, { name: "_resolution", set: "resolutions" ,short_code: common.dbUserMap['resolution'] }] },
            { db: "app_versions", metrics: [{ name: "_app_version", set: "app_versions", short_code: common.dbUserMap['app_version'] },{name:"_app_code",set:"app_codes",short_code:common.dbUserMap['app_code']}] },
            { db: "alias", metrics: [{ name: "_alias", set: "alias", short_code: common.dbUserMap['alias'] }] },
            { db: "locale", metrics: [{ name: "_locale", set: "locales", short_code: common.dbUserMap['locale'] },{ name: "_tz", set: "timezones", short_code: common.dbUserMap['time_zone'] }] }
        ];

        for (let i=0; i < predefinedMetrics.length; i++) {
            let tmpTimeObj = {},
                tmpSet = {},
                needsUpdate = false;

            for (let j=0; j < predefinedMetrics[i].metrics.length; j++) {
                let tmpMetric = predefinedMetrics[i].metrics[j],
                    recvMetricValue = params.qstring.metrics[tmpMetric.name];
                    recvMetricValue = common.toFirstUpper(recvMetricValue);

                if (recvMetricValue && (tmpMetric.name != '_alias')) {
                    let escapedMetricVal = recvMetricValue.replace(/^\$/, "");
                    needsUpdate = true;
                    tmpSet["meta." + tmpMetric.set] = escapedMetricVal;
                    common.fillTimeObject(params, tmpTimeObj, escapedMetricVal + '.' + common.dbMap['total']);

                    if (isNewUser) {
                        common.fillTimeObject(params, tmpTimeObj, escapedMetricVal + '.' + common.dbMap['new']);
                        common.fillTimeObject(params, tmpTimeObj, escapedMetricVal + '.' + common.dbMap['unique']);
                    } else if (tmpMetric.short_code && user[tmpMetric.short_code] != escapedMetricVal) {
                        common.fillTimeObject(params, tmpTimeObj, escapedMetricVal + '.' + common.dbMap['unique']);
                    } else {
                        for (let k=0; k < uniqueLevels.length; k++) {
                            tmpTimeObj[uniqueLevels[k] + '.' + escapedMetricVal + '.' + common.dbMap['unique']] = 1;
                        }
                    }

                    // Assign properties to app_users document of the current user
                    if (tmpMetric.short_code) {
                        if (isNewUser || (!isNewUser && user[tmpMetric.short_code] != escapedMetricVal)) {
                            userProps[tmpMetric.short_code] = escapedMetricVal;
                        }
                    }
                }
            }

            if (needsUpdate && predefinedMetrics[i].db!="") {
                //common.db.collection(predefinedMetrics[i].db).update({'_id': params.app_id}, {'$inc': tmpTimeObj, '$addToSet': tmpSet}, {'upsert': true});
            }
        }

        // sc: session count. common.dbUserMap is not used here for readability purposes.
        userProps['updatedAt'] = common.getCurrentEpochTime();
        common.db.collection('app_users' + params.app_id).update({'_id':params.app_user_id}, {'$inc':{'sc':1}, '$set':userProps}, {'upsert':true}, function () {
            updateTimelyUserSession(params);
        });

    }

    updateTimelyUserSession = function(params){

        let session={
            "st": params.time.timestamp
        }

        //Attach context place to the session.
        if(params.qstring.metrics && params.qstring.metrics.context && params.qstring.metrics.context.places._t){
            session.p = params.qstring.metrics.context.places._t;
        }
        let updatedAt = common.getCurrentEpochTime();
        common.db.collection('app_users' + params.app_id).update({'_id':params.app_user_id}, {'$addToSet':{'sessions':session}, '$set':{'updatedAt':updatedAt}},{'upsert': true, 'safe': true}, function () {

        });
    }

    /*
    function sendCallBack(trackId,appid,deviceid, refname){
        let client = require('node-rest-client').Client;
        let proxy = new client();

        if(trackId!==undefined && trackId!==""){

            //if we have the tracking id, gets its campaign id
            common.db.collection('campaignconversions_'+appid).findOne({"_id":common.db.ObjectID(trackId)},function(err,clickRecord){
                if(clickRecord){
                    //Get call back url associated with campaign.
                    common.db.collection('promoter_campaigns').findOne({"_id":common.db.ObjectID(clickRecord.campaignId)},function(err,campaign){
                        if(campaign){
                            url = campaign.callbackUrl + campaign.param +"="+clickRecord.clickId;
                             
                            let options = {
                                url: url,
                                method: 'GET',
                                qs: ''
                            }

                            //Start the request
                            request(options, function (error, response, body) {
                                if(error){
                                     
                                }
                                else{
                                  // write log file with response
                                  let fsData = {url:url, response:response.statusCode, data:body};
                                  usage.createCallBackLog(appid, fsData, "postback_installs");

                                  //Record click as confirmed.
                                  common.db.collection('campaignconversions_'+appid).update({"_id":common.db.ObjectID(trackId)},{$set: {isConfirmed: true,confirmedOn: common.getCurrentEpochTime(),did:deviceid,url:url,statusCode:response.statusCode}},{'upsert': true},function(err,updatedClick){

                                  });
                                }
                            });

                            cacheApi.setKey("app_id"+appid+refname,campaign.callbackUrl);
                             
                        }
                        else{
                             
                        }

                    });

                }
                else{
                     
                }
            });
        }
        // else{
        //
        //     proxy.post(url, function(data,response) {
        //         //Record click as confirmed.
        //          common.db.collection('campaignconversions_'+appid).update({"_id":common.db.ObjectID(trackId)},{$set: {isConfirmed: true,confirmedOn: common.getCurrentEpochTime(),did:deviceid,url:url,statusCode:response.statusCode}},{'upsert': true},function(err,updatedClick){
        //
        //          });
        //     });
        // }
    }
    */

    function sendCallBack(appid, deviceData){

       if(deviceData.click_transaction_id!==undefined && deviceData.click_transaction_id!==""){
           //if we have the tracking id, gets its campaign id
           common.db.collection('campaignconversions_'+appid).findOne({"_id":common.db.ObjectID(deviceData.click_transaction_id)},function(err,clickRecord){
               if(clickRecord){

                   // set campaign click time attributes
                   deviceData.session_device_ip = clickRecord.ip;
                   deviceData.click_device_model = (clickRecord.model)? clickRecord.model : '';
                   deviceData.click_device_brand = (clickRecord.device)? clickRecord.device : '';
                   deviceData.click_datetime = moment(clickRecord.createdOn*1000).format('DD-MM-YYYY HH:mm:ss');
                   deviceData.user_agent = clickRecord.userAgent;
                   deviceData.session_referrer = (clickRecord.referrer)? clickRecord.referrer : '';

                   //Get call back url associated with campaign.
                   common.db.collection('promoter_campaigns').findOne({"_id":common.db.ObjectID(clickRecord.campaignId)},function(err,campaign){
                       if(campaign){
                           // add question mark into url if does not exists
                           if(campaign.callbackUrl.indexOf('?') == -1){
                             campaign.callbackUrl = campaign.callbackUrl +'?';
                           }
                           url = campaign.callbackUrl + campaign.param +"="+deviceData.publisher_ref_id;
                           logger.info(`sendCallback :: ${url}`);
                           let options = {
                               url: url,
                               method: 'GET',
                               qs: deviceData
                           }

                           //Start the request
                           request(options, function (error, response, body) {
                               if(error){
                                logger.info(`response  :: +> ${response} && body ${body}`);
                                logger.error(`error=> ${error}`); 
                               }
                               else{
                                   // write log file with response
                                   let fsData = {url:url, response:response.statusCode, data:body};
                                   usage.createCallBackLog(appid, fsData, "postback_installs");

                                   //Record click as confirmed.
                                   common.db.collection('campaignconversions_'+appid).update({"_id":common.db.ObjectID(deviceData.click_transaction_id)},{$set: {isConfirmed: true,confirmedOn: common.getCurrentEpochTime(), did:deviceData.google_aid, url:url, statusCode:response.statusCode}},{'upsert': true},function(err,updatedClick){

                                   });
                               }
                           });
                           cacheApi.setKey("app_id"+appid+deviceData.publisherName,campaign.callbackUrl);
                         logger.info(`set callback url into app cache : ${appid}::::::${deviceData.campaign_id}`);
                       }
                       else{
                           logger.info(`campaign was not found for :: ${clickRecord.campaignId}`);
                       }
                   });

                   // prepare webhook data
                   postback.prepareWebHookData(appid, deviceData, 'installs', function(url, webHookData){
                       if(url && webHookData){
                           // send webhook data if setup webhook
                           postback.webHook(appid, url, webHookData)
                       }
                   });
               }
               else{
                logger.info(`Cannot find click record for ::${trackId}`);
               }
           });
       }
   }

    //This function registers new device to queue so that its intresets and gender gets calculated.
    function registerDeviceForProcessing(appid,did){
        /*let registerQueue = "https://sqs.us-east-1.amazonaws.com/064400875474/GenerateDeviceList_Alias";
        AWS.config.region = semusiConfig.AWS_REGION;
        AWS.config.update({accessKeyId:semusiConfig.AWS_ACCESS_KEY_ID,secretAccessKey:semusiConfig.AWS_SECRET_KEY});

        let device = {
            IsProduction:semusiConfig.IsProduction,
            appid:appid,
            did:did,
            time:common.getCurrentEpochTime()
        };
        let sqs = new AWS.SQS();
        let boxParams = {
            MessageBody: JSON.stringify(device),
            QueueUrl: registerQueue,
            DelaySeconds: 0
        };
        sqs.sendMessage(boxParams, function (err, data) {
            if (err) {
                 
                 
            }
            else {
                 
            }
        });*/
    }

    ///Kafka Version of API's
    usage.initialize = function(params){
        //Get location details from IP address.
        populateParamsWithLocationData(params);
        params.time = common.initTimeObj("",params.qstring.timestamp);

        if(params.qstring.skipPreviousDateCheck===undefined || params.qstring.skipPreviousDateCheck==="false"){
            let _tz = (params.qstring.metrics._tz)? params.qstring.metrics._tz: 0;
            if(params.qstring.dump == undefined){
                if(((params.time.timestamp + 10800) - _tz) < common.getCurrentEpochTime()){
                    params.time.timestamp = common.getCurrentEpochTime()+_tz;
                }
                if((params.time.timestamp - _tz) > common.getCurrentEpochTime()){
                    params.time.timestamp = common.getCurrentEpochTime()+_tz;
                }
            }
        }
        
        let updateSessions={};
        common.db.collection('app_users' + params.qstring.app_id).findOne({'_id': params.app_user_id }, function (err, dbAppUser){
            if(!err){
                if(dbAppUser){
                    //Check if it is a re install or a cache flush.
                    if(dbAppUser.inittime==params.qstring.inittime){
                        //Cache flush, so nothing required to update.
                        common.returnMessage(params, 200, 'Success', params.app_user_id);
                    }
                    else{
                        //Re Install
                        if(dbAppUser.history){
                            if(dbAppUser.history[dbAppUser.history.length-1]){
                                let appVersion = dbAppUser.av;
                                let osVersion = dbAppUser.pv;
                                let cc = dbAppUser.cc;
                                let cty = dbAppUser.cty;
                                let oldHistory = JSON.parse(JSON.stringify(dbAppUser.history[dbAppUser.history.length-1]));
                                let refName = dbAppUser.history[dbAppUser.history.length-1].refname;
                                let dtEntry = dbAppUser.history[dbAppUser.history.length-1].dtEntry;
                                let isNewInstall = (dbAppUser.history[dbAppUser.history.length-1].type == 'U')? true : false;
                                // prepare an object
                                createUpdateAppUserObject(dbAppUser,params);
                                if(params.qstring.python){
                                    delete dbAppUser['_id'];
                                }

                                // update source if current source is not organic (self)
                                if(refName != 'self'
                                  && refName != dbAppUser.history[dbAppUser.history.length-1].refname ){
                                     dbAppUser.history[dbAppUser.history.length-1] = oldHistory;
                                }

                                dbAppUser['updatedAt'] = common.getCurrentEpochTime();
                                dbAppUser['_app_pack'] = (params.qstring.metrics && params.qstring.metrics._app_package)? params.qstring.metrics._app_package : "";

                                common.db.collection('app_users' + params.qstring.app_id).update({'_id':params.app_user_id}, {'$set':dbAppUser}, function (err,result) {
                                    if(!err){
                                        let hisObject = dbAppUser.history[dbAppUser.history.length-1];
                                        if( ( cty != dbAppUser.cty || cc != dbAppUser.cc || appVersion != dbAppUser.av || osVersion != dbAppUser.pv || refName != dbAppUser.history[dbAppUser.history.length-1].refname ) && !isNewInstall){
                                            let appData = {};
                                            appData.p = dbAppUser.p;
                                            appData.av = appVersion;
                                            appData.pv = osVersion;
                                            appData.cc = cc;
                                            appData.cty = cty;
                                            appData.history = JSON.parse(JSON.stringify(dbAppUser.history));
                                            appData.history[dbAppUser.history.length-1].refname = refName;
                                            appData.history[dbAppUser.history.length-1].dtEntry = dtEntry;

                                            // substract aggregate collection
                                            common.substractTimlyDashboard(appData, params.qstring.app_id, function(result){
                                                if(result){
                                                    common.addTimlyDashboard(dbAppUser, params.qstring.app_id);
                                                }
                                            });
                                        }
                                        else if(isNewInstall){
                                            common.addTimlyDashboard(dbAppUser, params.qstring.app_id);
                                            common.manageActiveUser(params.qstring.app_id, dbAppUser.p, true, dbAppUser.did ); // manage active users into optimise collection
                                            common.usersActiveManage(params.qstring.app_id, dbAppUser.p, 'unknown', 1);

                                            // manage campaign aggregate collection
                                            common.manageCampaignAggregate(dbAppUser.p.toLowerCase(), dbAppUser.history[dbAppUser.history.length-1], params.qstring.app_id);
                                            // check active user sc boundary
                                            common.manageActiveSessionUser(params.qstring.app_id, params.app_user_id, 1, function(err, data){
                                                 
                                                if(data){
                                                    // manage user sc
                                                    if(data.scObj){
                                                        common.db.collection('timely_users_session').update({'_id': data.scId}, {'$inc': data.scObj}, {'upsert': true},function(err, response){
                                                            if(err){
                                                                logger.error(`timely_users_session sc err => ${err}`); 
                                                            }
                                                            else{
                                                               logger.info(`sc resppnse update :: ${response}`);  
                                                            }
                                                        });
                                                    }
                                                    // manage user session average length
                                                    if(data.aslObj){
                                                        common.db.collection('timely_users_session').update({'_id': data.aslId}, {'$inc': data.aslObj}, {'upsert': true},function(err, response){
                                                            if(err){
                                                                logger.error(`timely_users_session asl err => ${err}`);
                                                            }
                                                            else{
                                                                logger.info(`asl resppnse update :: ${response}`); 
                                                            }
                                                        });
                                                    }
                                                }
                                            });

                                            // add new user into PG
                                            if(common.config.IsProduction){
                                                // install new user into pgsql
                                                psqlUtils.addNewUser(dbAppUser, params.qstring.app_id)
                                                    .then(status =>{
                                                        logger.info(`status => ${status}`);
                                                    })
                                                    .catch(error =>{
                                                        logger.error(`error => ${error}`);    
                                                    });
                                             }

                                            // webhook attributes
                                            let installAttrs = {
                                                    app_name : params.qstring.app_name,
                                                    google_aid : dbAppUser.did,
                                                    package_name : dbAppUser._app_pack,
                                                    installer :  (hisObject.installer)? hisObject.installer : '',
                                                    os_id : dbAppUser.android_id,
                                                    conversion_datetime : moment(hisObject.dtEntry*1000).format('DD-MM-YYYY HH:mm:ss'),
                                                    device_model : dbAppUser.d,
                                                    device_brand : dbAppUser.d,
                                                    country_code : (dbAppUser.cc)? dbAppUser.cc : '',
                                                    device_carrier : dbAppUser.c,
                                                    device_ip: hisObject.ip_address,
                                                    app_version: dbAppUser.av.replace(/\_/g, "."),
                                                    os_version: dbAppUser.pv.replace(/\_/g, "."),
                                                    region: (dbAppUser.cty)? dbAppUser.cty : ''
                                                }

                                            //Hit callback url.
                                            if((hisObject.trackId!== undefined && hisObject.trackId!=="")){
                                                cacheApi.setKey("app_id"+params.qstring.app_id+dbAppUser.did, hisObject.clickId);
                                                 
                                                if(params.qstring.app_id == '594cc2a4b44cacc2397b40dc'){
                                                    logger.info(`set click id in cache:: ${params.qstring.app_id}  && ${dbAppUser.did} && ${hisObject.clickId}`);
                                                }
                                                // set other attributes for webhook
                                                installAttrs.campaign_id = (hisObject.utm_campaign)? hisObject.utm_campaign : '';
                                                installAttrs.publisher_ref_id = hisObject.clickId;
                                                installAttrs.click_transaction_id = hisObject.trackId;
                                                installAttrs.publisherName = hisObject.refname;

                                                // send to third party and app owner webhook
                                                sendCallBack(params.qstring.app_id, installAttrs);
                                            }
                                            else{
                                                // prepare webhook data
                                                postback.prepareWebHookData(params.qstring.app_id, installAttrs, 'installs', function(url, data){
                                                    if(url && data){
                                                        // send webhook data if setup webhook
                                                        postback.webHook(params.qstring.app_id, url, data)
                                                    }
                                                });
                                            }

                                            // make and send install event
                                            params.qstring.metrics.timestamp = params.qstring.timestamp;
                                            params.qstring.metrics.key = 'install';
                                            params.qstring.metrics.did = dbAppUser.did;
                                            params.qstring.metrics.user_id = params.app_user_id;
                                            params.qstring.metrics.eventTime = params.qstring.metrics.timestamp;
                                            params.qstring.metrics.rtime = common.getCurrentEpochTime();
                                            params.qstring.metrics.segment = dbAppUser;
                                            params.qstring.events = [params.qstring.metrics];

                                            // delete metrics object
                                            delete params.qstring.metrics;

                                            let obj = {
                                                appid : params.qstring.app_id,
                                                did : params.qstring.device_id,
                                                events : params.qstring.events,
                                                key : "events"
                                            };

                                            // send event to queue
                                            dataProducer.writeDataDirectly(obj, {});

                                        }

                                        //usage.createCallBackLog(params.qstring.app_id, dbAppUser, "re_installs_success");
                                         common.returnMessage(params, 200, 'Success', params.app_user_id);
                                    }
                                    else{
                                        // write logs into file
                                        usage.createCallBackLog(params.qstring.app_id, params.qstring, "installs_failed5");
                                        logger.error(`Initialize device err::   => ${err}`);
                                        common.returnMessage(params, 500, 'Failed to Initialize device.');
                                    }

                                });
                            }
                            else{
                                common.returnMessage(params, 200, 'Success');
                            }
                        }
                        else{
                            // write logs into file
                            usage.createCallBackLog(params.qstring.app_id, params.qstring, "installs_failed4");
                            common.returnMessage(params, 500, 'Failed to Initialize device.');
                        }
                    }
                }
                else{
                    //It is a new user.
                    dbAppUser = createNewAppUserObject(params);
                    if(params.qstring.dump){
                        dbAppUser.history[0].refname = params.qstring.dump;
                    }
                      //Set Clicks 
                    if(params.qstring.metrics._custom_Attribution_Branch){
                        dbAppUser['_custom_Attribution_Branch']=params.qstring.metrics._custom_Attribution_Branch;
                    }
                    dbAppUser['createdAt'] = common.getCurrentEpochTime();
                    dbAppUser['_app_pack'] = (params.qstring.metrics && params.qstring.metrics._app_package)? params.qstring.metrics._app_package : "";
                    
                    // check history array with object
                    if(dbAppUser.history && dbAppUser.history[0]){

                        dbAppUser.history[0] = common.validateRefname(dbAppUser.history[0]);
                         
                        common.db.collection('app_users' + params.qstring.app_id).update({'_id':params.app_user_id}, {'$set':dbAppUser}, {'upsert':true}, function (err,result) {
                            if(!err){
                                //common.fillTimeObject(params, updateSessions, common.dbMap['total']);
                                common.fillTimeObject(params, updateSessions, common.dbMap['new']);
                                common.fillTimeObject(params, updateSessions, common.dbMap['unique']);
                                common.db.collection('sessions').update({'_id': common.db.ObjectID(params.qstring.app_id)}, {'$inc': updateSessions}, {'upsert': true});
                                //Register new device to queue so that ML can process its gender and interests.
                                registerDeviceForProcessing(params.qstring.app_id,params.qstring.device_id);
                                common.addTimlyDashboard(dbAppUser, params.qstring.app_id);
                                // manage campaign aggregate collection information
                                common.manageCampaignAggregate(dbAppUser.p.toLowerCase(), dbAppUser.history[0], params.qstring.app_id);
                                // manage active users into optimise collection
                                common.manageActiveUser(params.qstring.app_id, dbAppUser.p, true, dbAppUser.did);
                                common.usersActiveManage(params.qstring.app_id, dbAppUser.p, 'unknown', 1);

                                // webhook attributes
                                let installAttrs = {
                                        app_name : params.qstring.app_name,
                                        google_aid : dbAppUser.did,
                                        package_name : dbAppUser._app_pack,
                                        installer :  (dbAppUser.history[0].installer)? dbAppUser.history[0].installer : '',
                                        os_id : dbAppUser.android_id,
                                        conversion_datetime : moment(dbAppUser.history[0].dtEntry*1000).format('DD-MM-YYYY HH:mm:ss'),
                                        device_model : dbAppUser.d,
                                        device_brand : dbAppUser.d,
                                        country_code : (dbAppUser.cc)? dbAppUser.cc : '',
                                        device_carrier : dbAppUser.c,
                                        device_ip: dbAppUser.history[0].ip_address,
                                        app_version: dbAppUser.av.replace(/\_/g, "."),
                                        os_version: dbAppUser.pv.replace(/\_/g, "."),
                                        region: (dbAppUser.cty)? dbAppUser.cty : ''
                                    }

                                //Hit callback url.
                                if((dbAppUser.history[0].trackId!== undefined && dbAppUser.history[0].trackId!=="")){
                                    cacheApi.setKey("app_id"+params.qstring.app_id+dbAppUser.did, dbAppUser.history[0].clickId);

                                    if(params.qstring.app_id == '594cc2a4b44cacc2397b40dc'){
                                        logger.info(`set click id in cache :: ${params.qstring.app_id} && ${dbAppUser.did} && ${dbAppUser.history[0].clickId}`)
                                    }

                                    // set other attributes for webhook
                                    installAttrs.campaign_id = (dbAppUser.history[0].utm_campaign)? dbAppUser.history[0].utm_campaign : '';
                                    installAttrs.publisher_ref_id = dbAppUser.history[0].clickId;
                                    installAttrs.click_transaction_id = dbAppUser.history[0].trackId;
                                    installAttrs.publisherName = dbAppUser.history[0].refname;

                                    // send to third party and app owner webhook
                                    sendCallBack(params.qstring.app_id, installAttrs);
                                }
                                else{
                                    // prepare webhook data
                                    postback.prepareWebHookData(params.qstring.app_id, installAttrs, 'installs', function(url, data){
                                        if(url && data){
                                            // send webhook data if setup webhook
                                            postback.webHook(params.qstring.app_id, url, data)
                                        }
                                    });
                                }

                                if(common.config.IsProduction){
                                    // install new user into pgsql
                                    psqlUtils.addNewUser(dbAppUser, params.qstring.app_id)
                                          .then(status =>{
                                            logger.info(`status => ${status}`);
                                        })
                                        .catch(error =>{
                                            logger.error(`error => ${error}`);
                                        });
                                }

                                // make and send install event
                                params.qstring.metrics.timestamp = params.qstring.timestamp;
                                params.qstring.metrics.key = 'install';
                                params.qstring.metrics.did = dbAppUser.did;
                                params.qstring.metrics.user_id = params.app_user_id;
                                params.qstring.metrics.eventTime = params.qstring.metrics.timestamp;
                                params.qstring.metrics.rtime = common.getCurrentEpochTime();
                                params.qstring.metrics.segment = dbAppUser;
                                params.qstring.events = [params.qstring.metrics];

                                // delete metrics object
                                delete params.qstring.metrics;

                                let obj = {
                                    appid : params.qstring.app_id,
                                    did : params.qstring.device_id,
                                    events : params.qstring.events,
                                    key : "events"
                                };

                                // send event to queue
                                dataProducer.writeDataDirectly(obj, {});

                                // return response of API
                                common.returnMessage(params, 200, 'Success', params.app_user_id);
                            }
                            else{
                                // write logs into file
                                usage.createCallBackLog(params.qstring.app_id, params.qstring, "installs_failed1");
                                logger.error(`Initialize err:: ${err}`);
                                common.returnMessage(params, 500, 'Failed to Initialize device.');
                            }
                        });
                    }
                    else{
                        // write logs into file
                        usage.createCallBackLog(params.qstring.app_id, params.qstring, "installs_failed2");
                         
                        common.returnMessage(params, 500, 'Failed to Initialize device.');
                    }
                }
            }
            else{
                // write logs into file
                usage.createCallBackLog(params.qstring.app_id, params.qstring, "installs_failed3");
                logger.error(`device err:: ${err}`);
                common.returnMessage(params, 500, 'Failed to Initialize device.');
            }
        });
    }


    //Create an app user from the API call params.
    function createNewAppUserObject(params){
        let appUser = {};
        appUser['inittime'] = params.qstring.inittime;
        appUser[common.dbUserMap['first_seen']] = params.time.timestamp;
        appUser[common.dbUserMap['last_seen']] = params.time.timestamp;
        appUser[common.dbUserMap['device_id']] = params.qstring.device_id;
        appUser[common.dbUserMap['city']] = params.user.city;
        appUser[common.dbUserMap['country_code']] = params.user.cc;
        appUser['country'] = params.user.country;
        appUser['region'] = params.user.region;
        appUser['active'] = true;
        //appUser['sc'] = 1;
        let session={
            "st": params.qstring.timestamp
        }
        //Attach context place to the session.
        //if(params.qstring.metrics && params.qstring.metrics.context && params.qstring.metrics.context.places._t){
          //  session.p = params.qstring.metrics.context.places._t;
        //}
        //appUser['sessions'] = [session];
        if(params.qstring.sdk_version){
            appUser[common.dbUserMap['sdk_version']] = params.qstring.sdk_version;
        }
        if(params.qstring.sdk_i_version){
            appUser['sdkiv'] = params.qstring.sdk_i_version;
        }
        if(params.qstring.android_id){
            appUser['android_id'] = params.qstring.android_id;
        }

        if(params.qstring.metrics){
            appUser.history = [getMetricValue(params,"_ref")];
            appUser[common.dbUserMap['platform']] = getMetricValue(params,"_os");
            appUser[common.dbUserMap['platform_version']] = getMetricValue(params,"_os_version");
            appUser[common.dbUserMap['app_version']] = getMetricValue(params,"_app_version");
            appUser[common.dbUserMap['app_code']] = getMetricValue(params,"_app_code");
            appUser[common.dbUserMap['device']] = getMetricValue(params,"_device");
            appUser[common.dbUserMap['carrier']] = getMetricValue(params,"_carrier");
            appUser[common.dbUserMap['resolution']] = getMetricValue(params,"_resolution");
            appUser[common.dbUserMap['locale']] = getMetricValue(params,"_locale");
            appUser[common.dbUserMap['time_zone']] = getMetricValue(params,"_tz");

            //In encrypted version of API's _osa is sent
            if(params.qstring.metrics['_osa']){
                appUser[common.dbUserMap['platform']] = getMetricValue(params,"_osa");
            }
        }
        //This will update more fields and will be used while we import data from third parties.
        if(params.qstring.moreInfo){
            updateAppUserDetails(params,appUser);
        }

        return appUser;

    }

    //Update an appuser from API call params.
    function createUpdateAppUserObject(dbAppUser,params){

        dbAppUser['inittime'] = params.qstring.inittime;
        dbAppUser[common.dbUserMap['last_seen']] = params.time.timestamp;
        dbAppUser[common.dbUserMap['city']] = params.user.city;
        dbAppUser[common.dbUserMap['country_code']] = params.user.cc;
        dbAppUser['country'] = params.user.country;
        dbAppUser['region'] = params.user.region;
        dbAppUser['active'] = true;

        let session={
            "st": params.qstring.timestamp
        }
        //Attach context place to the session.
        //if(params.qstring.metrics && params.qstring.metrics.context && params.qstring.metrics.context.places._t){
          //  session.p = params.qstring.metrics.context.places._t;
        //}
        //dbAppUser['sessions'].push(session);
        if(params.qstring.sdk_version){
            dbAppUser[common.dbUserMap['sdk_version']] = params.qstring.sdk_version;
        }
        if(params.qstring.sdk_i_version){
            dbAppUser['sdkiv'] = params.qstring.sdk_i_version;
        }
        if(params.qstring.android_id){
            dbAppUser['android_id'] = params.qstring.android_id;
        }

        if(params.qstring.metrics){
            let historyObj = getMetricValue(params,"_ref");
            let len = (dbAppUser.history)?dbAppUser.history.length:0;
            if(len>0){
                if(dbAppUser.history[len-1].type=="U"){
                    historyObj.isnew = false;
                    dbAppUser.history.push(historyObj)
                }
                else if(dbAppUser.history[len-1].type=="I"){
                    //Update referal
                    let obj = dbAppUser.history[len-1];
                    common.populateHistoryObject(params.qstring.metrics['_ref'],obj);
                    if(params.qstring.skipPreviousDateCheck==="true"){
                        obj.updatedBy = "callback";
                    }
                    else{
                        obj.updatedBy = "sdk";
                    }
                    if(obj.refname!="self"){
                        dbAppUser.history[len-1] = obj;
                    }
                    else if(obj.updatedBy == "sdk" && params.qstring.metrics['_installAtt']){
                        //try to get referal from install attribution.
                        common.populateHistoryObject(params.qstring.metrics['_installAtt'],obj);
                        if(obj.refname!="self" && obj.refname!=""){
                            dbAppUser.history[len-1] = obj;
                        }

                    }
                }
            }
            dbAppUser[common.dbUserMap['platform']] = getMetricValue(params,"_os");
            dbAppUser[common.dbUserMap['platform_version']] = getMetricValue(params,"_os_version");
            dbAppUser[common.dbUserMap['app_version']] = getMetricValue(params,"_app_version");
            dbAppUser[common.dbUserMap['app_code']] = getMetricValue(params,"_app_code");
            dbAppUser[common.dbUserMap['device']] = getMetricValue(params,"_device");
            dbAppUser[common.dbUserMap['carrier']] = getMetricValue(params,"_carrier");
            dbAppUser[common.dbUserMap['resolution']] = getMetricValue(params,"_resolution");
            dbAppUser[common.dbUserMap['locale']] = getMetricValue(params,"_locale");
            dbAppUser[common.dbUserMap['time_zone']] = getMetricValue(params,"_tz");

            //In encrypted version of API's _osa is sent
            if(params.qstring.metrics['_osa']){
                appUser[common.dbUserMap['platform']] = getMetricValue(params,"_osa");
            }
        }

        //This will update more fields and will be used while we import data from third parties.
        if(params.qstring.moreInfo){
            updateAppUserDetails(params,dbAppUser);
        }

    }

    function getMetricValue(params,metric){
        try{
            if(params.qstring.metrics[metric]){
                if(metric=="_ref"){
                    let refObj = {};
                    //Extract refname from utm_source
                    common.populateHistoryObject(params.qstring.metrics[metric],refObj);
                    refObj.installer = (params.qstring.metrics['_installer']) ? common.toFirstUpper(params.qstring.metrics['_installer']):"";
                    refObj.type = "I";
                    refObj.dtEntry = params.time.timestamp;
                    refObj.ip_address = (params.qstring.metrics['ip'])?params.qstring.metrics['ip'] : params.ip_address;
                    refObj.isnew = true;
                    if(params.qstring.metrics.context && params.qstring.metrics.context.who && params.qstring.metrics.context.who.network && params.qstring.metrics.context.who.network.ctype){
                        refObj.network = common.toFirstUpper(params.qstring.metrics.context.who.network.ctype);
                    }
                    if(params.qstring.skipPreviousDateCheck==="true"){
                        refObj.createdBy = "callback";
                    }
                    else{
                        refObj.createdBy = "sdk";
                        //If referal is self, try to get referal from install attribution.
                        if(refObj.refname=="self" && params.qstring.metrics['_installAtt']){
                            common.populateHistoryObject(params.qstring.metrics['_installAtt'],refObj);
                            if(refObj.refname==undefined || refObj.refname==""){
                                refObj.refname="self";
                            }

                        }
                    }

                    // try{
                    //     // replace correct ref name if call from python
                    //     if(params.qstring.python){
                    //         refObj.refname=params.qstring.metrics[metric];
                    //     }
                    // }
                    // catch(e){
                    //
                    // }

                    return refObj;
                }
                else if(metric=="_tz"){
                   return params.qstring.metrics[metric];
                }
                else{
                    return common.toFirstUpper(params.qstring.metrics[metric].replace(/^\$/, ""));
                }
            }
            else{
                switch(metric){
                    case "_os_version":
                        return "NA";
                        break;
                    case "_app_version":
                        return "NA";
                        break;
                    case "_tz":
                        return 0;
                        break;
                    case "default":
                        return "";
                        break;
                }
            }
        }
        catch(e){
            logger.error(`error=> ${e}`);
            return "";
        }
    }

    function updateAppUserDetails(params,appUser){
        try{
            let keys = Object.keys(params.qstring.moreInfo);
            let userfields=[];
            keys.forEach(function(key){
                appUser[key] = params.qstring.moreInfo[key];
                if(key.indexOf('_custom') > -1){
                    let customlet = {};
                    customVar.name = key;
                    customVar.type = typeof(params.qstring.moreInfo[key]);
                    userfields.push(customVar);
                }
            });

            if(userfields.length>0){
                if(userfields.length>0){
                    let createdAt = common.getCurrentEpochTime();
                    let updatedAt = common.getCurrentEpochTime();
                    common.db.collection('app_customfields').update({"_id":common.db.ObjectID(params.qstring.app_id)},{'$addToSet':{ 'fields': { '$each': userfields } }, '$set':{'createdAt':createdAt, 'updatedAt':updatedAt}}, {'upsert':true}, function () {});
                }
            }
        }
        catch(e){
            logger.error(`error=> ${e}`);
            logger.info(`params.qstring.moreInfo: ${params.qstring.moreInfo}`);
        }
    }

    function populateParamsWithLocationData(params){

        if(!populateLocationUsingMaxmind(params)){
            //Fallback to IP2Location db if it failed to fetch data.
            populateLocationUsingIP2Location(params);
        }
    }

    function populateLocationUsingMaxmind (params) {
        // Location of the user is retrieved using geoip-lite module from her IP address.
        let locationData = {};
        // get geodata
        let geodata = geoReader.getGeoDataSync(params.ip_address);
        let foundCity = false;
        if(geodata){
            if(geodata.country){
                params.user.cc = geodata.country.iso_code;
                params.user.country = geodata.country.names.en;
            }
            else{
                params.user.cc = 'Unknown';
                params.user.country = 'Unknown';
            }

            if(geodata.city){
                params.user.city = geodata.city.names.en;
                foundCity=true;
            }
            else{
              params.user.city = 'Unknown';
            }
            params.user.region="";
            if(geodata.location){
                params.user.lat  = geodata.location.latitude;
                params.user.lng = geodata.location.longitude;
            }
            else{
                params.user.lat = '';
                params.user.lng = '';
            }
        }
        else{
            params.user.cc = 'Unknown';
            params.user.country = 'Unknown';
            params.user.city = 'Unknown';
            params.user.region="";
            params.user.lat = '';
            params.user.lng = '';
        }

        return foundCity;
    }

    function populateLocationUsingIP2Location (params) {

        let result = ip2loc.IP2Location_get_all(params.ip_address);
        let locationData = {};
         

        if(result && result.city ){
            if (result.city=='-' || result.city=='' || result.city.toLowerCase()=='unknown' ){
                return false;
            }
            else{
                if(result.country_short){
                    params.user.cc = result.country_short;
                }
                else{
                    params.user.cc = 'Unknown';
                }
                if(result.country_long){
                    params.user.country = result.country_long;
                }
                else{
                    params.user.country = 'Unknown';
                }

                if(result.city){
                    params.user.city = result.city;
                }
                else{
                  params.user.city = 'Unknown';
                }

                if(result.region){
                    params.user.region = result.region;
                }
                else{
                    params.user.region = 'Unknown';
                }

                if(result.latitude && result.longitude){
                    params.user.lat = result.latitude;
                    params.user.lng = result.longitude;
                }
                else{
                    params.user.lat = '';
                    params.user.lng = '';
                }
                return true;
            }

        }
        else{
            return false;
        }

    }
    // write logs into file
    usage.createCallBackLog = function(app_id, data, fname){
        let logDir = semusiConfig.callbackLog;
        let fileName = logDir + "/"+fname+"_"+  app_id + "_" + moment().year()+''+(moment().month()+1)+''+moment().date()+".log";
        if(fname == "installs_log"){
            fileName = logDir + "/"+fname+"_"+  app_id + "_" + moment().year()+''+(moment().month()+1)+''+moment().date()+''+moment().format('H')+".log";
        }
        let log = fs.createWriteStream(fileName, {'flags': 'a'});
        // use {'flags': 'a'} to append and {'flags': 'w'} to erase and write a new file
        log.end(JSON.stringify(data,null,4)+",");
    }

}(usage));

module.exports = usage;
