'use strict';

const Joi = require('joi');
const Dayjs = require('dayjs');
const Rfr = require('rfr');
Dayjs.extend(require('dayjs/plugin/customParseFormat'));

const Common = Rfr('/shared/common');
const ComSrv = Rfr('/server/comsrv');
const Db = Rfr('/server/db');

const typeTable = {
    incoming: Common.ctIncoming,
    outgoing: Common.ctOutgoing,
    internal: Common.ctInternal
};

const resTable = { // not used now
    answered: Common.resAnswered,
    unanswered: Common.resNoAnswer,
    busy: Common.resBusy,
    failed: Common.resFailed,
    other: Common.resOther,
    cancelled: Common.resCancelled
};

const transformDate = (date, clamp = '') => {
    if (date) {
        date = Dayjs(date, 'YYYY-MM-DD');
        if (!date.isValid()) {
            throw new Error('Invalid date specified');
        }

        if (clamp === 'start') {
            date = date.startOf('day');
        } else if (clamp === 'end') {
            date = date.endOf('day')
        }

        return date;
    }
};

const sortableFields = ['source', 'destination', 'callDate', 'totalDuration', 'callDuration', 'callType', 'callResult'];
const selectableFields = ['id', 'calldate', 'calltype', 'callresult', 'source', 'destination', 'callduration', 'totalduration', 'dailyid', 'dailyct', 'uniqueid', 'comment', 'ivr'];

const getFilter = async filter => {
    const schema = Joi.object({
        source: Joi.string().pattern(/^\d+$/).max(128),
        destination: Joi.link('source'),
        any: Joi.link('source'),
        callTypes: Joi.number().min(0).max(Common.ctMaxSet),
        dateStart: Joi.string().pattern(/^[0-9]{1,}-[0-9]{1,2}-[0-9]{1,2}$/).max(64),
        dateEnd: Joi.link('dateStart'),
        id: Joi.string().max(100),

        wcSource: Joi.boolean().default(false),
        wcDestination: Joi.boolean().default(false),
        wcAny: Joi.boolean().default(false),

        limit: Joi.number().integer().min(1).max(100).default(15),
        page: Joi.number().integer().min(1).default(1),
        sort: Joi.string().valid(...sortableFields).default('callDate'),
        dir: Joi.string().valid('asc', 'desc').default('desc'),

        disableGrouping: Joi.number().min(0).max(1).default(0),
    })

    let res = {
        hasUserFilters: (!!filter.dateStart || !!filter.dateEnd || !!filter.callTypes ||
            !!filter.id || !!filter.source || !!filter.destination || !!filter.any ||
            !!filter.wcSource || !!filter.wcDestination || !!filter.wcAny),
        ...(await schema.validateAsync(filter))
    };

    return res;
}

const filterQueryBuilder = (qb, parms) => {
    if (parms.source) {
        if (parms.wcSource) { qb.where('source', 'like', '%' + parms.source + '%'); }
        else { qb.where('source', '=', parms.source); }
    }
    if (parms.destination) {
        if (parms.wcDestination) { qb.where('destination', 'like', '%' + parms.destination + '%'); }
        else { qb.where('destination', '=', parms.destination); }
    }
    if (parms.any) {
        if (parms.wcAny) { qb.where(function() { this.where('source', 'like', '%' + parms.any + '%').orWhere('destination', 'like', '%' + parms.any + '%') }); }
        else { qb.where(function() { this.where('source', '=', parms.any).orWhere('destination', '=', parms.any) }); }
    }

    if (parms.dateStart) { qb.where('calldate', '>=', transformDate(parms.dateStart, 'start')); }
    if (parms.dateEnd) { qb.where('calldate', '<=', transformDate(parms.dateEnd, 'end')); }
    if (parms.id) { qb.where('dailyct', parms.id); }

    if (parms.callTypes) {
        const a = [];
        [Common.ctIncoming, Common.ctOutgoing, Common.ctInternal].forEach( e => {
            if (parms.callTypes & (1 << e)) { a.push(e); }
        });
        if (a.length > 0) { qb.whereIn('calltype', a); }
    }
}

const groupQueryBuilder = (qb, parms) => {
   // special case: if grouping and sorting is enabled, also sort by date (desc)
   if (!parms.disableGrouping && parms.sort !== 'callDate') {
       qb.orderBy('calldate', 'desc');
   }
};

const getRawRecords = async (parms) => {
    const rec = await Db.query('ptable')
        .select(...selectableFields)
        .modify(filterQueryBuilder, parms)
        .offset((parms.page - 1) * parms.limit)
        .limit(parms.limit)
        .orderBy(parms.sort.toLowerCase(), parms.dir.toLowerCase())
        .modify(groupQueryBuilder, parms);

    return Common.toArray(rec);
}

const mapRawRecords = rec => {
    const f = (r => { return {
        callDate: Dayjs(r.calldate),
        callType: r.calltype,
        callResult: r.callresult,
        source: r.source,
        destination: r.destination,
        callDuration: r.callduration,
        totalDuration: r.totalduration,
        dailyID: r.dailyid,
        dailyCT: r.dailyct,
        uniqueID: r.uniqueid,
        comment: (r.comment || ''),
        ivr: (r.ivr || 0)
    }});

    return rec.map(f);
};


const countSinceLastUpdate = async lastUpdateAt => {
    const res = await Db.query('ptable')
        .where('calldate', '>', lastUpdateAt)
        .count({num: 'calldate'});

    return res[0].num;
};

const getLatest = async () => {
    const res = Common.toArray(await Db.query('ptable')
        .select('calldate')
        .orderBy('calldate', 'desc')
        .first()
    );

    return res[0].calldate;
};

const countRecords = async (parms) => {
    const res = await Db.query('ptable')
        .modify(filterQueryBuilder, parms)
        .count({num: 'uniqueid'}).first();

    return res.num;
};

// Check if 1st record extends "0th" record by date
const isFirstCont = async (parms, mapped) => {
    const first = mapped[0];
    if (first) {
        const res = await Db.query('ptable')
            .select(...selectableFields)
            .modify(filterQueryBuilder, parms)
            .modify( (qb, parms) => {
                if (parms.sort === 'callDate' && parms.dir === 'asc') {
                    qb.where('calldate', '<', first.callDate)
                    .andWhere('calldate', '>=', Dayjs(first.callDate).startOf('day'));
                } else {
                    qb.where('calldate', '>', first.callDate)
                    .andWhere('calldate', '<=', Dayjs(first.callDate).endOf('day'));
                }

                if (parms.sort !== 'callDate') {
                    qb.where(parms.sort.toLowerCase(), '=', first[parms.sort]);
                }
            }, parms);

        if (res.length >= 1) {
            return true;
        }
    }

    return false;
}


const getDailyStats = async (parms, mapped) => {
    const allDays = mapped.map( r => Dayjs(r.callDate).startOf('day').valueOf() )
        .filter( (e, i, a) => a.indexOf(e) === i);

    let result = [];

    for (const e of allDays) {
        const res = await Db.query('ptable')
            .sum({ total_duration_sum: 'totalduration', call_duration_sum: 'callduration' })
            .count({ total_calls: 'uniqueid' })
            .modify(filterQueryBuilder, parms)
            .modify( (qb, parms) => {
                qb.where('calldate', '>=', Dayjs(e).startOf('day'))
                    .andWhere('calldate', '<=', Dayjs(e).endOf('day'));
            }, parms);

        result.push({
            day: e,
            totalDuration: res[0].total_duration_sum,
            callDuration: res[0].call_duration_sum,
            totalCalls: res[0].total_calls
        });
    }

    return result;
}

const getCdr = async (filter) => {
    const count = await countRecords(filter);
    const raw = await getRawRecords(filter);
    const mapped = mapRawRecords(raw);

    return {
        dailyStats: (await getDailyStats(filter, mapped)),
        isFirstCont: (!filter.disableGrouping && (await isFirstCont(filter, mapped))),
        maxPages: (filter.limit > 0 && count > 0) ? Math.ceil(count / filter.limit) : 1,
        thisPage: filter.page,
        records: mapped,
        filter: filter,
        latest: (await getLatest())
    }
};




const getCdrById = async (id) => {
    const raw = Common.toArray(await Db.query('ptable')
        .select(...selectableFields)
        .where('uniqueid', id)
        .first()
    );

    const mapped = mapRawRecords(raw);

    return {
        ...mapped[0],
        date: Dayjs(mapped[0].callDate).format('YYYY-MM-DD_HH-mm-ss'),
    };
}


const queryHandler = async (request, h) => {
    return await getCdr(await getFilter(request.query));
};

const checkNewHandler = async(request, h) => {
    return {
        numSinceLastUpdate: await countSinceLastUpdate(Dayjs(request.query.lastUpdateAt))
    };
};


module.exports = {
    getCdr: getCdr,
    getFilter: getFilter,
    getCdrById: getCdrById,

    routes: [{
        method: 'GET',
        path: '/api/query',
        handler: queryHandler
    },
    {
        method: 'GET',
        path: '/api/check-new',
        handler: checkNewHandler,
        options: {
            validate: {
                query: Joi.object({
                    lastUpdateAt: Joi.date().timestamp('unix').required()
                })
            }
        }
    }]
};
