/*
  Сервис интеграции ПП Парус 8 с WEB API
  Модуль ядра: протоколирование работы
*/

//----------------------
// Подключение библиотек
//----------------------

const { validateObject, getNowString, deepClone, makeErrorText } = require("./utils"); //Вспомогательные функции
const db = require("./db_connector"); //Модуль взаимодействия с БД
const { LogFileWriter } = require("./log_file_writer"); //Запись протокола в файл
const {
    SCONSOLE_LOG_COLOR_PATTERN_ERR,
    SCONSOLE_LOG_COLOR_PATTERN_WRN,
    SCONSOLE_LOG_COLOR_PATTERN_INF,
    SLOG_PREFIX_INF,
    SLOG_PREFIX_WRN,
    SLOG_PREFIX_ERR,
    SLOG_PREFIX_PROTOCOL_ERR
} = require("./constants"); //Общие константы
const { NLOG_STATE_INF, NLOG_STATE_WRN, NLOG_STATE_ERR } = require("../models/obj_log"); //Схемы валидации записи журнала работы сервиса обмена
const prmsLoggerSchema = require("../models/prms_logger"); //Схемы валидации параметров функций модуля

//--------------------------
// Глобальные идентификаторы
//--------------------------

//Интервал повторного сообщения об ошибке передачи строки лога по IPC (мс)
const NLOG_IPC_ERROR_REPORT_INTERVAL = 10000;

//Сообщение об останове сервера
const SMSG_SERVER_STOPPED = "Сервер приложений остановлен";

//------------
// Тело модуля
//------------

//Признак работы в дочернем процессе обработчика
const isWorkerProcess = () => typeof process.send === "function" && !!process.connected;

//Формирование строки лога без ANSI-оформления
const formatLogLine = (sNow, sPrefix, sMsg) => `${sNow} ${sPrefix}: ${sMsg}`;

//Оформление сообщения по типу записи
const resolveLogStyle = nLogState => {
    let sPrefix = SLOG_PREFIX_INF;
    let sColorPattern = "";
    switch (nLogState) {
        case NLOG_STATE_ERR: {
            sPrefix = SLOG_PREFIX_ERR;
            sColorPattern = SCONSOLE_LOG_COLOR_PATTERN_ERR;
            break;
        }
        case NLOG_STATE_WRN: {
            sPrefix = SLOG_PREFIX_WRN;
            sColorPattern = SCONSOLE_LOG_COLOR_PATTERN_WRN;
            break;
        }
        case NLOG_STATE_INF: {
            sPrefix = SLOG_PREFIX_INF;
            sColorPattern = SCONSOLE_LOG_COLOR_PATTERN_INF;
            break;
        }
        default:
            break;
    }
    return { sPrefix, sColorPattern };
};

//Класс управления протоколом
class Logger {
    //Конструктор класса
    constructor() {
        this.dbConnector = null;
        this.bLogDB = false;
        this.fileWriter = null;
        this.bStopLogged = false;
        this.nLastIpcErrorAt = 0;
    }
    //Включение/выключение записи протоколов в БД
    setLogDB(bLogDB) {
        if (this.dbConnector) this.bLogDB = bLogDB;
    }
    //Установка объекта для протоколирования в БД
    setDBConnector(dbConnector, bLogDB) {
        if (dbConnector instanceof db.DBConnector) {
            this.dbConnector = dbConnector;
            if (bLogDB === true) this.setLogDB(true);
            else this.setLogDB(false);
        }
    }
    //Удаление объекта для протоколирования в БД
    removeDBConnector() {
        this.dbConnector = null;
        this.setLogDB(false);
    }
    //Открытие файла лога (пустой путь - не открываем)
    openLogFile(sLogPath) {
        //Нет пути - протоколирование в файл не ведётся
        if (!sLogPath || !String(sLogPath).trim()) {
            this.bStopLogged = false;
            return;
        }
        //Создаём писатель при первом реальном открытии
        if (!this.fileWriter) this.fileWriter = new LogFileWriter();
        this.fileWriter.open(sLogPath);
        this.bStopLogged = false;
    }
    //Уведомление в консоль о протоколировании в файл (без записи в файл)
    notifyFileLogConsole() {
        if (this.fileWriter) this.fileWriter.notifyOpenedConsole();
    }
    //Корректное закрытие файла лога
    async closeLogFile() {
        if (this.fileWriter) await this.fileWriter.close();
    }
    //Аварийное синхронное освобождение файла лога
    closeLogFileSync() {
        if (this.fileWriter) this.fileWriter.closeSync();
    }
    //Признак, что сообщение об останове уже выдано
    wasStopLogged() {
        return this.bStopLogged === true;
    }
    //Фиксация выдачи сообщения об останове
    markStopLogged() {
        this.bStopLogged = true;
    }
    //Подготовка сообщения об останове сервера
    prepareServerStoppedMsg(nCode) {
        const nExitCode = typeof nCode === "number" ? nCode : 0;
        this.markStopLogged();
        return `${SMSG_SERVER_STOPPED} (код: ${nExitCode})`;
    }
    //Выдача сообщения об останове сервера
    async notifyServerStopped(nCode) {
        await this.warn(this.prepareServerStoppedMsg(nCode));
    }
    //Синхронная выдача сообщения об останове сервера (авария / process.on("exit"))
    notifyServerStoppedSync(nCode) {
        this.warnSync(this.prepareServerStoppedMsg(nCode));
    }
    //Сообщение об ошибке передачи строки лога по IPC
    reportIpcLogError(e) {
        const nNow = Date.now();
        if (nNow - this.nLastIpcErrorAt < NLOG_IPC_ERROR_REPORT_INTERVAL) return;
        this.nLastIpcErrorAt = nNow;
        console.log(SCONSOLE_LOG_COLOR_PATTERN_ERR, `${getNowString()} ${SLOG_PREFIX_PROTOCOL_ERR} (IPC): `, makeErrorText(e));
    }
    //Отправка строки лога родителю (воркер)
    sendLogLineToParent(prms) {
        if (typeof process.send !== "function" || !process.connected) return;
        if (!prms || typeof prms.sMsg !== "string") return;
        try {
            process.send({ ...prms, bLogLine: true }, err => {
                if (err) this.reportIpcLogError(err);
            });
        } catch (e) {
            this.reportIpcLogError(e);
        }
    }
    //Приём строки лога от воркера
    acceptWorkerLogLine(prms) {
        if (!prms || typeof prms.sNow !== "string" || typeof prms.sPrefix !== "string" || typeof prms.sMsg !== "string") return;
        this.emitLogLine(prms.nLogState, prms.sMsg, prms.sNow, prms.sPrefix);
    }
    //Выдача строки протокола: воркер - IPC; main - файл, затем console
    emitLogLine(nLogState, sMsg, sNow, sPrefix, bSync) {
        const style = resolveLogStyle(nLogState);
        const sPrefixUse = sPrefix || style.sPrefix;
        //Воркер - только IPC родителю (файл и console пишет main)
        if (isWorkerProcess()) {
            this.sendLogLineToParent({ nLogState, sNow, sPrefix: sPrefixUse, sMsg });
            return;
        }
        //Main: сначала файл, затем console
        const sLine = formatLogLine(sNow, sPrefixUse, sMsg);
        if (this.fileWriter) {
            if (bSync) this.fileWriter.appendSync(sLine);
            else this.fileWriter.enqueue(sLine);
        }
        console.log(style.sColorPattern, `${sNow} ${sPrefixUse}: `, sMsg);
    }
    //Синхронное протоколирование без БД (авария / финал)
    logSync(nLogState, sMsg) {
        this.emitLogLine(nLogState, sMsg, getNowString(), undefined, true);
    }
    //Синхронное протоколирование ошибки
    errorSync(sMsg) {
        this.logSync(NLOG_STATE_ERR, sMsg);
    }
    //Синхронное протоколирование предупреждения
    warnSync(sMsg) {
        this.logSync(NLOG_STATE_WRN, sMsg);
    }
    //Синхронное протоколирование информации
    infoSync(sMsg) {
        this.logSync(NLOG_STATE_INF, sMsg);
    }
    //Протоколирование
    async log(prms) {
        //Фиксируем время
        const sNow = getNowString();
        //Проверяем структуру переданного объекта для подключения
        let sCheckResult = validateObject(prms, prmsLoggerSchema.log, "Параметры функции протоколирования");
        //Если структура объекта в норме
        if (!sCheckResult) {
            this.emitLogLine(prms.nLogState, prms.sMsg, sNow);
            //Протоколируем в БД, если это необходимо
            if (this.bLogDB) {
                try {
                    //Если есть чем протоколировать
                    if (this.dbConnector && this.dbConnector.bConnected) {
                        await this.dbConnector.putLog(prms);
                    }
                } catch (e) {
                    this.emitLogLine(NLOG_STATE_ERR, e.sMessage, sNow, SLOG_PREFIX_PROTOCOL_ERR);
                    try {
                        if (this.dbConnector && this.dbConnector.bConnected)
                            await this.dbConnector.putLog({ nLogState: NLOG_STATE_ERR, sMsg: `${SLOG_PREFIX_PROTOCOL_ERR}: ${e.sMessage}` });
                    } catch {}
                }
            }
        } else {
            this.emitLogLine(NLOG_STATE_ERR, sCheckResult, sNow, SLOG_PREFIX_PROTOCOL_ERR);
            console.log(prms);
        }
    }
    //Протоколирование ошибки
    async error(sMsg, prms) {
        //Подготовим параметры для протоколирования
        let logData = prms ? deepClone(prms) : {};
        //Выставим сообщение и тип записи журнала
        logData.nLogState = NLOG_STATE_ERR;
        logData.sMsg = sMsg;
        //Протоколируем
        await this.log(logData);
    }
    //Протоколирование предупреждения
    async warn(sMsg, prms) {
        //Подготовим параметры для протоколирования
        let logData = prms ? deepClone(prms) : {};
        //Выставим сообщение и тип записи журнала
        logData.nLogState = NLOG_STATE_WRN;
        logData.sMsg = sMsg;
        //Протоколируем
        await this.log(logData);
    }
    //Протоколирование информации
    async info(sMsg, prms) {
        //Подготовим параметры для протоколирования
        let logData = prms ? deepClone(prms) : {};
        //Выставим сообщение и тип записи журнала
        logData.nLogState = NLOG_STATE_INF;
        logData.sMsg = sMsg;
        //Протоколируем
        await this.log(logData);
    }
}

//-----------------
// Интерфейс модуля
//-----------------

exports.Logger = Logger;
