/*
  Сервис интеграции ПП Парус 8 с WEB API
  Дополнительный модуль: Интеграция с ЭДО "ДИАДОК" (DIADOC)
*/

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

const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
const { httpRequest } = require("../core/http_client"); //Работа с HTTP/HTTPS запросами
const config = require("../config"); //Параметры сервера
const { SDDAUTH_API_CLIENT_ID, SDEPARTMENT_NAME, SDEPARTMENT_ID } = require("./diadoc_config"); //Ключ разработчика

//---------------------
// Глобальные константы
//---------------------

// Список тегов которые должны содержать массив
const tag = [
    "DocumentAttachments",
    "Metadata",
    "Signatures",
    "CorrectionRequests",
    "Receipts",
    "Resolutions",
    "XmlSignatureRejections",
    "RecipientTitles",
    "Requests",
    "UniversalMessages",
    "InitialDocumentIds"
];

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

//формирования адреса запроса для получения данных о текущей организации пользователя
const buildMyOrganizationURL = srvRoot => {
    return `${srvRoot.replace(/\/+$/, "")}/GetMyOrganizations`;
};

//формирования адреса запроса для получения данных о организации по идентификатору ящика
const buildOrganizationBoxIdURL = srvRoot => {
    return `${srvRoot.replace(/\/+$/, "")}/GetBox`;
};

//формирования адреса запроса для получения данных о организации по ИНН/КПП
const buildOrganizationURL = srvRoot => {
    return `${srvRoot.replace(/\/+$/, "")}/GetOrganization`;
};

//формирования адреса запроса для получения данных о организации по ИНН/КПП
const buildOrganizationsByInnKppURL = srvRoot => {
    return `${srvRoot.replace(/\/+$/, "")}/GetOrganizationsByInnKpp`;
};

//Обернуть содержимое тега в массив
const toArray = (obj, tags) => {
    for (const prop in obj) {
        let value = obj[prop];
        if (typeof value === "object") {
            obj[prop] = toArray(value, tag);
        }
        if (tags.indexOf(prop) != -1 && !Array.isArray(obj[prop])) {
            obj[prop] = JSON.parse("[" + JSON.stringify(value) + "]");
        }
    }
    return obj;
};

//Конвертация в XML
const toXML = obj => {
    const builder = new xml2js.Builder();
    return builder.buildObject(obj);
};

//Конвертация в XML
const parseXML = xmlDoc => {
    return new Promise((resolve, reject) => {
        xml2js.parseString(xmlDoc, { explicitArray: false, mergeAttrs: true }, (err, result) => {
            if (err) reject(err);
            else resolve(result);
        });
    });
};

//Конвертация в JSON
const toJSON = async obj => {
    let result = await parseXML(obj);
    result = result.root;
    toArray(result, tag);
    return result;
};

//Добавление определённого количетсва часов к дате
const addHours = (dDate, nHours) => {
    dDate.setTime(dDate.getTime() + nHours * 60 * 60 * 1000);
    return new Date(dDate);
};

//Получение ключа разработчика по организации/принадлежности
const getAPIClientId = (nCompany, nJurPers = null) => {
    //Ключи в diadoc_conf.js заданы
    if (SDDAUTH_API_CLIENT_ID) {
        //И это массив
        if (Array.isArray(SDDAUTH_API_CLIENT_ID)) {
            //Найдем значение в массиве
            let keyFound = SDDAUTH_API_CLIENT_ID.find(keyItem => keyItem.nCompany === nCompany && keyItem.nJurPers === nJurPers);
            //Найдено совпадение в массиве
            if (keyFound) return keyFound.sAPIClientId;
            else {
                //Ищем с принадлежностью по умолчанию
                keyFound = SDDAUTH_API_CLIENT_ID.find(keyItem => keyItem.nCompany === nCompany && !keyItem.nJurPers);
                //Найдено совпадение в массиве
                if (keyFound) return keyFound.sAPIClientId;
                else return null;
            }
        } else return SDDAUTH_API_CLIENT_ID;
    } else return null;
};

//Проверка ключа разработчика
const checkAPIClientId = sAPIClientId => {
    if (!sAPIClientId) {
        throw new Error('Не задан ключ разработчика. Запросите его у поставщика услуг ЭДО "ДИАДОК" и укажите в "./modules/diadoc_config.js".');
    }
};

//Формиорвание заголовка сообщения
const buildHeaders = (sAPIClientId, sToken = null) => ({
    "Content-type": "application/json",
    "X-Solution-Info": `PARUS_${config.common.sRelease}`,
    Authorization: `DiadocAuth ddauth_api_client_id=${sAPIClientId}${sToken ? `,ddauth_token=${sToken}` : ""}`,
    Accept: "application/json"
});

//Отбор организций
const getOrganizations = (organizations, sNumEdo = null) => {
    //Параметры отбора
    let isRoaming = false;
    let isActive = true;
    //Итоговая выборка
    let organization = { Organizations: [] };
    //Задан код участника ЭДО
    if (sNumEdo) {
        //Найдем активную организацию не в роуминге
        organization.Organizations[0] = organizations.Organizations.find(org => org.FnsParticipantId === sNumEdo);
    }
    //Не удалось получить организацию по коду участника ЭДО
    if (!organization.Organizations[0]) {
        //Найдем активную организацию не в роуминге
        organization.Organizations[0] = organizations.Organizations.find(org => org.IsRoaming === isRoaming && org.IsActive === isActive);
    }
    //Не удалось получить организацию не в роуминге
    if (!organization.Organizations[0]) {
        //Найдем активную организацию
        organization.Organizations[0] = organizations.Organizations.find(org => org.IsActive === isActive);
    }
    //Если не удалось получить активную организацию
    if (!organization.Organizations[0]) {
        //Если нет организации не в роуминге и найдено более одной организации
        if (organizations.Organizations.length > 1) {
            throw Error(`Найдено несколько организаций в роуминге.`);
        } else {
            //Вернем единственную найденую организацию
            return organizations[0];
        }
    } else {
        //Вернем найденую организацию
        return organization;
    }
};

//Получение организации по ИНН/КПП контрагента
const getOrganization = async (sSrvRoot, headers, nInn, nKpp) => {
    //Параметры запроса
    let httpRequestOptions;
    let serverResp;
    //Формируем запрос для получения BoxId
    httpRequestOptions = {
        uri: buildOrganizationURL(sSrvRoot),
        qs: {
            inn: nInn,
            kpp: nKpp
        },
        headers: headers,
        json: true
    };
    //Выполним запрос
    serverResp = { Organizations: [await httpRequest(httpRequestOptions)] };
    return serverResp;
};

//Получение ящика организации по ИНН/КПП контрагента
const getOrganizationBoxId = async (sSrvRoot, headers, nInn, nKpp, sNumEdo) => {
    //Параметры запроса
    let httpRequestOptions;
    let serverResp;
    let organization = {};
    //Формируем запрос для получения BoxId по ИНН/КПП
    httpRequestOptions = {
        uri: buildOrganizationsByInnKppURL(sSrvRoot),
        qs: {
            inn: nInn,
            kpp: nKpp
        },
        headers: headers,
        json: true
    };
    try {
        //Выполним запрос
        serverResp = await httpRequest(httpRequestOptions);
        try {
            //Получим организацию не в роуминге (или единственную организацию в роуминге)
            serverResp = getOrganizations(serverResp, sNumEdo);
            if (!serverResp?.Organizations[0]) {
                throw Error(
                    `Не удалось получить ящик получателя для контрагента с ${sNumEdo ? `кодом участника ЭДО: ${sNumEdo}, ` : ""}ИНН: ${nInn} и КПП: ${nKpp}`
                );
            }
        } catch (e) {
            //Получим головную организацию по ИНН/КПП
            serverResp = await getOrganization(sSrvRoot, headers, nInn, nKpp);
        }
        //Проверим соответствие КПП организации
        if (serverResp?.Organizations[0]?.Kpp != nKpp && serverResp?.Organizations[0]) {
            //Если КПП не соответстует заданному - проверим, что в полученной организации есть департамент с заданным КПП
            for (let i in serverResp.Organizations[0].Departments) {
                //Если найден подходящий департамент - запомним идентификатор и выходим из цикла
                if (serverResp.Organizations[0].Departments[i]?.Kpp == nKpp) {
                    //Сохраняем полученный ответ
                    organization.DepartmentId = serverResp.Organizations[0].Departments[i].DepartmentId;
                    break;
                }
            }
        }
        //Не удалось получить ящик получателя или полученая организация не соответствует заданному ИНН
        if (!serverResp?.Organizations[0]?.Boxes[0]?.BoxId || serverResp?.Organizations[0]?.Inn != nInn) {
            throw new Error(`Не удалось получить ящик получателя для контрагента с ИНН: ${nInn} и КПП: ${nKpp}`);
        }
        //Не удалось получить департаментом с соответствующим КПП
        if (serverResp?.Organizations[0]?.Kpp != nKpp && !organization?.DepartmentId) {
            throw new Error(
                `Не удалось получить ящик получателя для контрагента с ИНН: ${nInn} и КПП: ${nKpp}, у головной организации отсутствует подразделение с КПП: ${nKpp}`
            );
        }
        //Сохраняем полученный ответ
        organization.BoxId = serverResp.Organizations[0].Boxes[0].BoxId;
        return organization;
    } catch (e) {
        throw Error(`Ошибка при получении ящика получателя:  ${e.message}`);
    }
};

//Обработчик "До" подключения к сервису
const beforeConnect = async prms => {
    //Подготовим параметры аутентификации
    const loginAtribute = "login";
    const passAtribute = "password";
    let surl = prms.options.url;
    surl = `${surl}?type=password`;
    //Получим ключ разработчика
    let sAPIClientId = getAPIClientId(prms.options.nCompany, prms.options.nJurPers);
    //Проверим ключ разработчика
    checkAPIClientId(sAPIClientId);
    //Сформируем запрос на аутентификацию
    return {
        options: {
            headers: buildHeaders(sAPIClientId),
            url: surl,
            body: JSON.stringify({
                [loginAtribute]: prms.service.sSrvUser,
                [passAtribute]: prms.service.sSrvPass
            }),
            simple: false
        }
    };
};

//Обработчик "После" подключения к сервису
const afterConnect = async prms => {
    //Если пришла ошибка
    if (prms.optionsResp.statusCode != 200) {
        throw new Error(prms.queue.blResp.toString());
    } else {
        //Сохраним полученный токен доступа в контекст сервиса
        return {
            blResp: Buffer.from(prms.queue.blResp),
            sCtx: prms.queue.blResp.toString(),
            dCtxExp: addHours(new Date(), 23)
        };
    }
};

//Обработчик "До" отправки запроса на экспорт документа к сервису "ДИАДОК"
const beforeMessagePost = async prms => {
    //Получим ключ разработчика
    let sAPIClientId = getAPIClientId(prms.options.nCompany, prms.options.nJurPers);
    //Проверим ключ разработчика
    checkAPIClientId(sAPIClientId);
    //Формируем запрос
    try {
        //Считаем токен доступа из контекста сервиса
        let sToken = null;
        if (prms.service.sCtx) {
            sToken = prms.service.sCtx;
        }
        //Если не достали из контекста токен доступа - значит нет аутентификации на сервере
        if (!sToken) return { bUnAuth: true };
        //Получим параметры запроса
        const optionsData = await toJSON(prms.queue.sOptions);
        //Конвертируем XML из "Парус 8" в JSON
        let obj = await toJSON(prms.queue.blMsg.toString());
        //Формируем запрос для получения FromBoxId
        let httpRequestOptions = {
            uri: buildMyOrganizationURL(prms.service.sSrvRoot),
            headers: buildHeaders(sAPIClientId, sToken),
            json: true
        };
        //Переменные для запросов
        let serverResp;
        let organization;
        try {
            //Выполним запрос
            serverResp = await httpRequest(httpRequestOptions);
            //Получим идентификатор организации по ИНН/КПП поставщика документа
            for (let i in serverResp.Organizations) {
                //Если найдена подходящая организация - запомним идентификатор и выходим из цикла
                if (serverResp.Organizations[i].Inn == optionsData.inn_pr && serverResp.Organizations[i].Kpp == optionsData.kpp_pr) {
                    //Сохраняем полученный ответ
                    obj.FromBoxId = serverResp.Organizations[i].Boxes[0].BoxId;
                    break;
                }
            }
            //Не удалось получить ящик отправителя
            if (!obj.FromBoxId) {
                throw new Error(`Не удалось получить ящик текущей организации с ИНН: ${optionsData.inn_pr} и КПП: ${optionsData.kpp_pr}`);
            }
        } catch (e) {
            throw Error(`Ошибка при получении ящика текущей организации:  ${e.message}`);
        }
        //Получим ящик получателя
        organization = await getOrganizationBoxId(
            prms.service.sSrvRoot,
            buildHeaders(sAPIClientId, sToken),
            optionsData.inn_cs,
            optionsData.kpp_cs,
            optionsData.num_cs
        );
        obj.ToBoxId = organization.BoxId;
        //Если не заполнен идентификатор подразделения и при получении ящика удалось его подобрать
        if (!obj.ToDepartmentId && organization.DepartmentId) {
            obj.ToDepartmentId = organization.DepartmentId;
        }
        //Если пришел ответ
        if (prms.queue.blResp && serverResp.statusCode == 200) {
            //Вернем загруженный документ
            return {
                blResp: prms.queue.blResp
            };
        }
        //Собираем и отдаём общий результат работы
        return {
            options: {
                headers: buildHeaders(sAPIClientId, sToken),
                simple: false
            },
            blMsg: Buffer.from(JSON.stringify(obj))
        };
    } catch (e) {
        throw Error(e);
    }
};

//Обработчик "После" запроса на экспорт документа к сервису "ДИАДОК"
const afterMessagePost = async prms => {
    //Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
    let resu = null;
    //Действие выполнено успешно
    if (prms.optionsResp.statusCode == 200) {
        try {
            resu = toXML(JSON.parse(prms.queue.blResp.toString()));
        } catch (e) {
            throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК". Ошибка интерпретации: ${e.message}`);
        }
    } else {
        //Если пришел текст ошибки
        if (prms.queue.blResp) {
            throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК": ${prms.queue.blResp.toString()}`);
        } else {
            throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ');
        }
    }
    //Возврат результата
    return {
        blResp: Buffer.from(resu)
    };
};

//Обработчик "До" отправки запроса на экспорт патча документа к сервису "ДИАДОК"
const beforeMessagePatchPost = async prms => {
    //Получим ключ разработчика
    let sAPIClientId = getAPIClientId(prms.options.nCompany, prms.options.nJurPers);
    //Проверим ключ разработчика
    checkAPIClientId(sAPIClientId);
    //Формируем запрос
    try {
        //Считаем токен доступа из контекста сервиса
        let sToken = null;
        if (prms.service.sCtx) {
            sToken = prms.service.sCtx;
        }
        //Если не достали из контекста токен доступа - значит нет аутентификации на сервере
        if (!sToken) return { bUnAuth: true };
        //Конвертируем XML из "Парус 8" в понятный "ДИАДОК" JSON
        let obj = await toJSON(prms.queue.blMsg.toString());
        //Собираем и отдаём общий результат работы
        return {
            options: {
                headers: buildHeaders(sAPIClientId, sToken),
                simple: false
            },
            blMsg: Buffer.from(JSON.stringify(obj))
        };
    } catch (e) {
        throw Error(e);
    }
};

//Обработчик "После" запроса на экспорт патча документа к сервису "ДИАДОК"
const afterMessagePatchPost = async prms => {
    let resu = null;
    //Действие выполнено успешно
    if (prms.optionsResp.statusCode == 200) {
        try {
            //Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
            resu = toXML(JSON.parse(prms.queue.blResp.toString()));
        } catch (e) {
            throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК". Ошибка интерпретации: ${e.message}`);
        }
    } else {
        //Если пришел текст ошибки
        if (prms.queue.blResp) {
            throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК": ${prms.queue.blResp.toString()}`);
        } else {
            throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ');
        }
    }
    //Возврат результата
    return {
        blResp: Buffer.from(resu)
    };
};

//Обработчик "До" отправки запроса на получение новых событий к сервису "ДИАДОК"
const beforeEvent = async prms => {
    //Получим ключ разработчика
    let sAPIClientId = getAPIClientId(prms.options.nCompany, prms.options.nJurPers);
    //Проверим ключ разработчика
    checkAPIClientId(sAPIClientId);
    //Формируем запрос
    try {
        let sToken = null; //Токен доступа
        let surl = prms.options.url; //Адрес запрос
        let serverResp; //Результат запроса информации по текущей организации
        let obj; //Тело запроса (JSON)
        let rblMsg; //Буфер тела запроса
        let sBoxId; //Идентификатор ящика текущей организации
        let sDepartmentId; //Идентификатор подразделения
        //Считаем токен доступа из контекста сервиса
        if (prms.service.sCtx) {
            sToken = prms.service.sCtx;
        }
        //Если не достали из контекста токен доступа - значит нет аутентификации на сервере
        if (!sToken) return { bUnAuth: true };
        //Получим параметры запроса
        const optionsData = await toJSON(prms.queue.sOptions);
        //Формируем запрос для получения BoxId
        let httpRequestOptions = {
            uri: buildMyOrganizationURL(prms.service.sSrvRoot),
            headers: buildHeaders(sAPIClientId, sToken),
            json: true
        };
        try {
            //Выполним запрос
            serverResp = await httpRequest(httpRequestOptions);
            //Получим идентификатор организации по ИНН/КПП контрагента организации
            for (let i in serverResp.Organizations) {
                //Если найдена подходящая организация - запомним идентификатор и выходим из цикла
                if (serverResp.Organizations[i].Inn == optionsData.inn && serverResp.Organizations[i].Kpp == optionsData.kpp) {
                    //Сохраняем полученный ответ
                    sBoxId = serverResp.Organizations[i].Boxes[0].BoxId;
                    //Если задано подразделение
                    if (prms.options.sdepartment_name) {
                        if (prms.options.sdepartment_name == SDEPARTMENT_NAME) {
                            sDepartmentId = SDEPARTMENT_ID;
                        } else {
                            //Получим идентификатор подразделения
                            for (let j in serverResp.Organizations[i].Departments) {
                                //Если нашлось подразделение - запомним идентификатор и выходим из цикла
                                if (serverResp.Organizations[i].Departments[j].Name == prms.options.sdepartment_name) {
                                    sDepartmentId = serverResp.Organizations[i].Departments[j].DepartmentId;
                                    break;
                                }
                            }
                            //Не удалось получить идентификатор подразделения
                            if (!sDepartmentId) {
                                throw new Error(`Не удалось получить идентификатор подразделения с наименованием "${prms.options.sdepartment_name}"`);
                            }
                        }
                    }
                    break;
                }
            }
            //Не удалось получить ящик текущей организации
            if (!sBoxId) {
                throw new Error(`Не удалось получить ящик текущей организации с ИНН: ${optionsData.inn} и КПП: ${optionsData.kpp}`);
            }
        } catch (e) {
            throw Error(`Ошибка при получении ящика текущей организации: ${e.message}`);
        }
        //Сформируем адрес запроса
        surl = `${surl}?boxId=${sBoxId}`;
        //Если действие не "Документооборот"
        if (prms.options.saction != "DOCFLOWS") {
            //Заполним параметры для отбора последних событий
            if (prms.options.aftereventid) {
                surl = `${surl}&afterEventId=${prms.options.aftereventid}`;
            } else {
                surl = `${surl}&timestampFromTicks=${prms.options.timestampfromticks}`;
            }
            //Заполним идентификатор подразделения
            if (prms.options.sdepartment_name && sDepartmentId) {
                surl = `${surl}&departmentId=${sDepartmentId}`;
            }
            //Заполним максимальное количество возвращаемых элементов
            if (prms.options.limit) {
                surl = `${surl}&limit=${prms.options.limit}`;
            }
        } else {
            if (prms.queue.blMsg) {
                //Конвертируем XML из "Парус 8" в понятный "ДИАДОК" JSON
                obj = await toJSON(prms.queue.blMsg.toString());
                rblMsg = Buffer.from(JSON.stringify(obj));
            }
        }
        //Собираем и отдаём общий результат работы
        return {
            options: {
                headers: buildHeaders(sAPIClientId, sToken),
                simple: false,
                url: surl,
                boxId: sBoxId
            },
            blMsg: rblMsg
        };
    } catch (e) {
        throw Error(e);
    }
};

//Обработчик "После" запроса на получение новых событий к сервису "ДИАДОК"
const afterEvent = async prms => {
    let sAPIClientId = null; //Ключ разработчика
    let sToken = null; //Токен доступа
    let resu = null; //Ответ сервера
    let httpRequestOptions = null; //Параметры для запроса информации по ящику
    let serverResp; //Результат запроса информации по организации
    let resp = null; //Ответ сервера
    let box = null; //Информация ящика
    let boxIds = { boxIds: [] }; //Список уникальных ящиков контрагентов
    //Действие выполнено успешно
    if (prms.optionsResp.statusCode == 200) {
        //Получим ключ разработчика
        sAPIClientId = getAPIClientId(prms.options.nCompany, prms.options.nJurPers);
        //Проверим ключ разработчика
        checkAPIClientId(sAPIClientId);
        //Разберем ответ сервера
        resp = JSON.parse(prms.queue.blResp.toString());
        //Получим список уникальных ящиков
        for (let i in resp.Events) {
            if (resp.Events[i]?.Message) {
                if (!boxIds.boxIds.find(box => box.boxId === resp.Events[i]?.Message.FromBoxId) && resp.Events[i]?.Message.FromBoxId) {
                    boxIds.boxIds.push({ boxId: resp.Events[i]?.Message.FromBoxId });
                }
                if (!boxIds.boxIds.find(box => box.boxId === resp.Events[i]?.Message.ToBoxId) && resp.Events[i]?.Message.ToBoxId) {
                    boxIds.boxIds.push({ boxId: resp.Events[i]?.Message.ToBoxId });
                }
            }
        }
        //Если требуется выполнить запросы доп. информации по ящикам организации
        if (boxIds.boxIds.length) {
            //Считаем токен доступа из контекста сервиса
            if (prms.service.sCtx) {
                sToken = prms.service.sCtx;
            }
            //Если не достали из контекста токен доступа - значит нет аутентификации на сервере
            if (!sToken) return { bUnAuth: true };
            for (let i in boxIds.boxIds) {
                httpRequestOptions = null;
                //Формируем запрос для получения BoxId
                httpRequestOptions = {
                    uri: buildOrganizationBoxIdURL(prms.service.sSrvRoot),
                    headers: buildHeaders(sAPIClientId, sToken),
                    qs: {
                        boxId: boxIds.boxIds[i].boxId
                    },
                    json: true
                };
                try {
                    //Выполним запрос
                    serverResp = await httpRequest(httpRequestOptions);
                    if (serverResp?.Organization) {
                        //Запишем полученную информацию о контрагенте
                        boxIds.boxIds[i].Inn = serverResp?.Organization?.Inn;
                        boxIds.boxIds[i].Kpp = serverResp?.Organization?.Kpp;
                        boxIds.boxIds[i].FullName = serverResp?.Organization?.FullName;
                        boxIds.boxIds[i].ShortName = serverResp?.Organization?.ShortName;
                        boxIds.boxIds[i].FnsParticipantId = serverResp?.Organization?.FnsParticipantId;
                    } else {
                        //Не удалось получить информацию о контрагенте по ящику организации
                        throw new Error(`Не удалось получить информацию о контрагенте по ящику организации ${boxIds.boxIds[i].boxId}`);
                    }
                } catch (e) {
                    throw Error(`Ошибка при получении информации о контрагенте по ящику организации: ${e.message}`);
                }
            }
            //Добавим к ответу информацию о контрагентах ящика
            for (let i in resp.Events) {
                if (resp.Events[i]?.Message) {
                    //Если надо добавить информацию по ящику-отправителю
                    box = boxIds.boxIds.find(box => box.boxId === resp.Events[i]?.Message.FromBoxId);
                    if (box) {
                        resp.Events[i].Message.FromInn = box.Inn;
                        resp.Events[i].Message.FromKpp = box.Kpp;
                        resp.Events[i].Message.FromFullName = box.FullName;
                        resp.Events[i].Message.FromShortName = box.ShortName;
                        resp.Events[i].Message.FromFnsParticipantId = box.FnsParticipantId;
                    }
                    //Если надо добавить информацию по ящику-получателю
                    box = boxIds.boxIds.find(box => box.boxId === resp.Events[i]?.Message.ToBoxId);
                    if (box) {
                        resp.Events[i].Message.ToInn = box.Inn;
                        resp.Events[i].Message.ToKpp = box.Kpp;
                        resp.Events[i].Message.ToFullName = box.FullName;
                        resp.Events[i].Message.ToShortName = box.ShortName;
                        resp.Events[i].Message.ToFnsParticipantId = box.FnsParticipantId;
                    }
                }
            }
        }
        try {
            //Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
            resu = toXML({ root: resp });
        } catch (e) {
            throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК". Ошибка интерпретации: ${e.message}`);
        }
    } else {
        //Если пришел текст ошибки
        if (prms.queue.blResp) {
            throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК": ${prms.queue.blResp.toString()}`);
        } else {
            throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ');
        }
    }
    //Возврат результата
    return {
        blResp: Buffer.from(resu)
    };
};

//Обработчик "До" отправки запроса на загрузку вложения
const beforeDocLoad = async prms => {
    //Получим ключ разработчика
    let sAPIClientId = getAPIClientId(prms.options.nCompany, prms.options.nJurPers);
    //Проверим ключ разработчика
    checkAPIClientId(sAPIClientId);
    //Формируем запрос
    try {
        //Считаем токен доступа из контекста сервиса
        let sToken = null;
        if (prms.service.sCtx) {
            sToken = prms.service.sCtx;
        }
        //Если не достали из контекста токен доступа - значит нет аутентификации на сервере
        if (!sToken) return { bUnAuth: true };
        let surl = prms.options.url;
        let entId;
        let msgId = "messageId=";
        //В зависимости от режима загрузки определим наименование узла
        switch (prms.options.type) {
            //Загрузка файла
            case 0:
                entId = "entityId=";
                break;
            //Загрузка PDF
            case 1:
                entId = "documentId=";
                break;
            //Загрузка Извещения о получении
            case 2:
                entId = "attachmentId=";
                break;
            //Загрузка Уведомления об уточнении
            case 3:
                entId = "attachmentId=";
                break;
            //Загрузка Титул отказа от подписи документа
            case 4:
                entId = "attachmentId=";
                break;
            //Загрузка Титула покупателя документа
            case 5:
                entId = "documentId=";
                msgId = "letterId=";
                break;
            //Загрузка Универсального сообщения
            case 6:
                entId = "attachmentId=";
                break;
            default:
        }
        surl = `${surl}?${msgId}${prms.options.smsgid}&${entId}${prms.options.sentid}`;
        let obj;
        let rblMsg;
        if (prms.queue.blMsg && prms.options.type != 5 && prms.options.type != 6) {
            //Конвертируем XML из "Парус 8" в понятный "ДИАДОК" JSON
            obj = await toJSON(prms.queue.blMsg.toString());
            rblMsg = Buffer.from(JSON.stringify(obj));
        } else {
            if (prms.queue.blMsg) {
                rblMsg = prms.queue.blMsg;
            }
        }
        //Собираем и отдаём общий результат работы
        return {
            options: {
                qs: {
                    boxId: prms.options.sboxid,
                    documentTypeNamedId: prms.options.documentTypeNamedId,
                    documentFunction: prms.options.documentFunction,
                    documentVersion: prms.options.documentVersion,
                    titleIndex: prms.options.titleIndex
                },
                headers: buildHeaders(sAPIClientId, sToken),
                url: surl,
                simple: false
            },
            blMsg: rblMsg
        };
    } catch (e) {
        throw Error(e);
    }
};

//Обработчик "После" отправки запроса на загрузку вложения
const afterDocLoad = async prms => {
    if (prms.queue.blResp) {
        //Если выполнено без ошибок и не требуется повторный запрос
        if ((prms.optionsResp.statusCode == 200 || prms.optionsResp.statusCode == 404) && !prms.optionsResp.headers["retry-after"]) {
            return;
        } else {
            let iterable = [1, 2, 3, 4, 5];
            let serverResp;
            //Если не превышает лимита запросов
            for (let value of iterable) {
                if (prms.optionsResp.statusCode != 200 || prms.optionsResp.headers["retry-after"]) {
                    //Если загружаем PDF
                    if (prms.options.type == 1 && prms.optionsResp.headers["retry-after"]) {
                        await new Promise(resolve => setTimeout(resolve, (Number(prms.optionsResp.headers["retry-after"]) + 1) * 1000));
                    } else {
                        await new Promise(resolve => setTimeout(resolve, 2000));
                    }
                    //Выполним повторный запрос
                    serverResp = await httpRequest(prms.options);
                    //Сохраняем полученный ответ
                    prms.queue.blResp = Buffer.from(serverResp.body || "");
                    prms.optionsResp.statusCode = serverResp.statusCode;
                    prms.optionsResp.headers = serverResp.headers;
                    //Если пришел ответ
                    if (prms.queue.blResp && serverResp.statusCode == 200) {
                        //Вернем загруженный документ
                        return {
                            optionsResp: prms.optionsResp,
                            blResp: prms.queue.blResp
                        };
                    }
                }
            }
            //Если был ответ от сервера с ошибкой (иначе мы сюда не попадём)
            if (prms.queue.blResp) {
                //Разберем сообщение об ошибке
                throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК": ${prms.queue.blResp.toString()}`);
            }
        }
    } else {
        throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ');
    }
    //Возврат результата
    return;
};

//Обработчик "До" отправки запроса на удаление документа к сервису "ДИАДОК"
const beforeDocDelete = async prms => {
    //Получим ключ разработчика
    let sAPIClientId = getAPIClientId(prms.options.nCompany, prms.options.nJurPers);
    //Проверим ключ разработчика
    checkAPIClientId(sAPIClientId);
    //Формируем запрос
    try {
        //Считаем токен доступа из контекста сервиса
        let sToken = null;
        if (prms.service.sCtx) {
            sToken = prms.service.sCtx;
        }
        //Если не достали из контекста токен доступа - значит нет аутентификации на сервере
        if (!sToken) return { bUnAuth: true };
        //Собираем и отдаём общий результат работы
        return {
            options: {
                headers: buildHeaders(sAPIClientId, sToken),
                simple: false
            }
        };
    } catch (e) {
        throw Error(e);
    }
};

//Обработчик "До" отправки запроса на получение списка подразделений контрагента к сервису "ДИАДОК"
const beforeDepartmentIdGet = async prms => {
    //Получим ключ разработчика
    let sAPIClientId = getAPIClientId(prms.options.nCompany, prms.options.nJurPers);
    //Проверим ключ разработчика
    checkAPIClientId(sAPIClientId);
    //Формируем запрос
    try {
        //Считаем токен доступа из контекста сервиса
        let sToken = null;
        if (prms.service.sCtx) {
            sToken = prms.service.sCtx;
        }
        //Если не достали из контекста токен доступа - значит нет аутентификации на сервере
        if (!sToken) return { bUnAuth: true };
        //Собираем и отдаём общий результат работы
        return {
            options: {
                headers: buildHeaders(sAPIClientId, sToken),
                simple: false
            }
        };
    } catch (e) {
        throw Error(e);
    }
};

//Обработчик "После" отправки запроса на получение списка подразделений контрагента к сервису "ДИАДОК"
const afterDepartmentIdGet = async prms => {
    let resu = null;
    let organization = {};
    //Получим параметры запроса
    const optionsData = await toJSON(prms.queue.sOptions);
    //Действие выполнено успешно
    if (prms.optionsResp.statusCode == 200) {
        try {
            try {
                //Получим организацию не в роуминге (или единственную организацию в роуминге)
                organization = getOrganizations(JSON.parse(prms.queue.blResp.toString()), optionsData.sNumEdo);
                if (!organization) {
                    throw Error(
                        `Не удалось получить ящик для контрагента с ${optionsData.sNumEdo ? `кодом участника ЭДО: ${optionsData.sNumEdo}, ` : ""}ИНН: ${optionsData.nINN} и КПП: ${optionsData.nKPP}`
                    );
                }
            } catch (e) {
                //Получим ключ разработчика
                let sAPIClientId = getAPIClientId(prms.options.nCompany, prms.options.nJurPers);
                //Считаем токен доступа из контекста сервиса
                let sToken = prms.service.sCtx;
                //Получим головную организацию по ИНН/КПП
                organization = await getOrganization(prms.service.sSrvRoot, buildHeaders(sAPIClientId, sToken), optionsData.nINN, optionsData.nKPP);
            }
            //Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
            resu = toXML({ root: organization });
        } catch (e) {
            throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК". Ошибка интерпретации: ${e.message}`);
        }
    } else {
        //Если пришел текст ошибки
        if (prms.queue.blResp) {
            throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК": ${prms.queue.blResp.toString()}`);
        } else {
            throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ');
        }
    }
    //Возврат результата
    return {
        blResp: Buffer.from(resu)
    };
};

//Обработчик "До" отправки запроса на получение списка документов в сообщении к сервису "ДИАДОК"
const beforeDocumentsByMessageIdGet = async prms => {
    //Получим ключ разработчика
    let sAPIClientId = getAPIClientId(prms.options.nCompany, prms.options.nJurPers);
    //Проверим ключ разработчика
    checkAPIClientId(sAPIClientId);
    //Формируем запрос
    try {
        //Считаем токен доступа из контекста сервиса
        let sToken = null;
        if (prms.service.sCtx) {
            sToken = prms.service.sCtx;
        }
        //Если не достали из контекста токен доступа - значит нет аутентификации на сервере
        if (!sToken) return { bUnAuth: true };
        //Собираем и отдаём общий результат работы
        return {
            options: {
                headers: buildHeaders(sAPIClientId, sToken),
                simple: false
            }
        };
    } catch (e) {
        throw Error(e);
    }
};

//Обработчик "После" отправки запроса на получение списка документов в сообщении к сервису "ДИАДОК"
const afterDocumentsByMessageIdGet = async prms => {
    let resu = null;
    //Действие выполнено успешно
    if (prms.optionsResp.statusCode == 200) {
        try {
            //Разберем ответ сервера
            resp = JSON.parse(prms.queue.blResp.toString());
            //Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
            resu = toXML({ root: resp });
        } catch (e) {
            throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК". Ошибка интерпретации: ${e.message}`);
        }
    } else {
        //Если пришел текст ошибки
        if (prms.queue.blResp) {
            throw new Error(`Неожиданный ответ сервера ЭДО "ДИАДОК": ${prms.queue.blResp.toString()}`);
        } else {
            throw new Error('Сервер ЭДО "ДИАДОК" не вернул ответ');
        }
    }
    //Возврат результата
    return {
        blResp: Buffer.from(resu)
    };
};

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

exports.beforeConnect = beforeConnect;
exports.afterConnect = afterConnect;
exports.beforeMessagePost = beforeMessagePost;
exports.afterMessagePost = afterMessagePost;
exports.beforeMessagePatchPost = beforeMessagePatchPost;
exports.afterMessagePatchPost = afterMessagePatchPost;
exports.beforeEvent = beforeEvent;
exports.afterEvent = afterEvent;
exports.beforeDocLoad = beforeDocLoad;
exports.afterDocLoad = afterDocLoad;
exports.beforeDocDelete = beforeDocDelete;
exports.beforeDepartmentIdGet = beforeDepartmentIdGet;
exports.afterDepartmentIdGet = afterDepartmentIdGet;
exports.beforeDocumentsByMessageIdGet = beforeDocumentsByMessageIdGet;
exports.afterDocumentsByMessageIdGet = afterDocumentsByMessageIdGet;
