// Copyright (C) 2019-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT

const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
const logFactory = require('./log');
const { ArgumentError } = require('./exceptions');
const { LogType } = require('./enums');

const WORKING_TIME_THRESHOLD = 100000; // ms, 1.66 min

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

class LoggerStorage {
    constructor() {
        this.clientID = Date.now().toString().substr(-6);
        this.lastLogTime = Date.now();
        this.workingTime = 0;
        this.collection = [];
        this.ignoreRules = {}; // by event
        this.isActiveChecker = null;
        this.saving = false;

        this.ignoreRules[LogType.zoomImage] = {
            lastLog: null,
            timeThreshold: 1000,
            ignore(previousLog) {
                return Date.now() - previousLog.time < this.timeThreshold;
            },
        };

        this.ignoreRules[LogType.changeAttribute] = {
            lastLog: null,
            ignore(previousLog, currentPayload) {
                return (
                    currentPayload.object_id === previousLog.payload.object_id &&
                    currentPayload.id === previousLog.payload.id
                );
            },
        };
    }

    updateWorkingTime() {
        if (!this.isActiveChecker || this.isActiveChecker()) {
            const lastLogTime = Date.now();
            const diff = lastLogTime - this.lastLogTime;
            this.workingTime += diff < WORKING_TIME_THRESHOLD ? diff : 0;
            this.lastLogTime = lastLogTime;
        }
    }

    async configure(isActiveChecker, activityHelper) {
        const result = await PluginRegistry.apiWrapper.call(
            this,
            LoggerStorage.prototype.configure,
            isActiveChecker,
            activityHelper,
        );
        return result;
    }

    async log(logType, payload = {}, wait = false) {
        const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.log, logType, payload, wait);
        return result;
    }

    async save() {
        const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.save);
        return result;
    }
}

LoggerStorage.prototype.configure.implementation = function (isActiveChecker, userActivityCallback) {
    if (typeof isActiveChecker !== 'function') {
        throw new ArgumentError('isActiveChecker argument must be callable');
    }

    if (!Array.isArray(userActivityCallback)) {
        throw new ArgumentError('userActivityCallback argument must be an array');
    }

    this.isActiveChecker = () => !!isActiveChecker();
    userActivityCallback.push(this.updateWorkingTime.bind(this));
};

LoggerStorage.prototype.log.implementation = function (logType, payload, wait) {
    if (typeof payload !== 'object') {
        throw new ArgumentError('Payload must be an object');
    }

    if (typeof wait !== 'boolean') {
        throw new ArgumentError('Payload must be an object');
    }

    if (logType in this.ignoreRules) {
        const ignoreRule = this.ignoreRules[logType];
        const { lastLog } = ignoreRule;
        if (lastLog && ignoreRule.ignore(lastLog, payload)) {
            lastLog.payload = {
                ...lastLog.payload,
                ...payload,
            };

            this.updateWorkingTime();
            return ignoreRule.lastLog;
        }
    }

    const logPayload = { ...payload };
    logPayload.client_id = this.clientID;
    if (this.isActiveChecker) {
        logPayload.is_active = this.isActiveChecker();
    }

    const log = logFactory(logType, { ...logPayload });
    if (logType in this.ignoreRules) {
        this.ignoreRules[logType].lastLog = log;
    }

    const pushEvent = () => {
        this.updateWorkingTime();
        log.validatePayload();
        log.onClose(null);
        this.collection.push(log);
    };

    if (log.type === LogType.sendException) {
        serverProxy.server.exception(log.dump()).catch(() => {
            pushEvent();
        });

        return log;
    }

    if (wait) {
        log.onClose(pushEvent);
    } else {
        pushEvent();
    }

    return log;
};

LoggerStorage.prototype.save.implementation = async function () {
    while (this.saving) {
        await sleep(100);
    }

    const collectionToSend = [...this.collection];
    const lastLog = this.collection[this.collection.length - 1];

    const logPayload = {};
    logPayload.client_id = this.clientID;
    logPayload.working_time = this.workingTime;
    if (this.isActiveChecker) {
        logPayload.is_active = this.isActiveChecker();
    }

    if (lastLog && lastLog.type === LogType.sendTaskInfo) {
        logPayload.job_id = lastLog.payload.job_id;
        logPayload.task_id = lastLog.payload.task_id;
    }

    const userActivityLog = logFactory(LogType.sendUserActivity, logPayload);
    collectionToSend.push(userActivityLog);

    try {
        this.saving = true;
        await serverProxy.logs.save(collectionToSend.map((log) => log.dump()));
        for (const rule of Object.values(this.ignoreRules)) {
            rule.lastLog = null;
        }
        this.collection = [];
        this.workingTime = 0;
        this.lastLogTime = Date.now();
    } finally {
        this.saving = false;
    }
};

module.exports = new LoggerStorage();
