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

(() => {
    const FormData = require('form-data');
    const { ServerError } = require('./exceptions');
    const store = require('store');
    const config = require('./config');
    const DownloadWorker = require('./download.worker');

    function generateError(errorData) {
        if (errorData.response) {
            const message = `${errorData.message}. ${JSON.stringify(errorData.response.data) || ''}.`;
            return new ServerError(message, errorData.response.status);
        }

        // Server is unavailable (no any response)
        const message = `${errorData.message}.`; // usually is "Error Network"
        return new ServerError(message, 0);
    }

    class WorkerWrappedAxios {
        constructor() {
            const worker = new DownloadWorker();
            const requests = {};
            let requestId = 0;

            worker.onmessage = (e) => {
                if (e.data.id in requests) {
                    if (e.data.isSuccess) {
                        requests[e.data.id].resolve(e.data.responseData);
                    } else {
                        requests[e.data.id].reject({
                            error: e.data.error,
                            response: {
                                status: e.data.status,
                                data: e.data.responseData,
                            },
                        });
                    }

                    delete requests[e.data.id];
                }
            };

            worker.onerror = (e) => {
                if (e.data.id in requests) {
                    requests[e.data.id].reject(e);
                    delete requests[e.data.id];
                }
            };

            function getRequestId() {
                return requestId++;
            }

            async function get(url, requestConfig) {
                return new Promise((resolve, reject) => {
                    const newRequestId = getRequestId();
                    requests[newRequestId] = {
                        resolve,
                        reject,
                    };
                    worker.postMessage({
                        url,
                        config: requestConfig,
                        id: newRequestId,
                    });
                });
            }

            Object.defineProperties(
                this,
                Object.freeze({
                    get: {
                        value: get,
                        writable: false,
                    },
                }),
            );
        }
    }

    class ServerProxy {
        constructor() {
            const Axios = require('axios');
            Axios.defaults.withCredentials = true;
            Axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN';
            Axios.defaults.xsrfCookieName = 'csrftoken';
            const workerAxios = new WorkerWrappedAxios();

            let token = store.get('token');
            if (token) {
                Axios.defaults.headers.common.Authorization = `Token ${token}`;
            }

            async function about() {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/server/about`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function share(directory) {
                const { backendAPI } = config;
                directory = encodeURIComponent(directory);

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/server/share?directory=${directory}`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function exception(exceptionObject) {
                const { backendAPI } = config;

                try {
                    await Axios.post(`${backendAPI}/server/exception`, JSON.stringify(exceptionObject), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function formats() {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/server/annotation/formats`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function userAgreements() {
                const { backendAPI } = config;
                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/restrictions/user-agreements`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function register(username, firstName, lastName, email, password1, password2, confirmations) {
                let response = null;
                try {
                    const data = JSON.stringify({
                        username,
                        first_name: firstName,
                        last_name: lastName,
                        email,
                        password1,
                        password2,
                        confirmations,
                    });
                    response = await Axios.post(`${config.backendAPI}/auth/register`, data, {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function login(username, password) {
                const authenticationData = [
                    `${encodeURIComponent('username')}=${encodeURIComponent(username)}`,
                    `${encodeURIComponent('password')}=${encodeURIComponent(password)}`,
                ]
                    .join('&')
                    .replace(/%20/g, '+');

                Axios.defaults.headers.common.Authorization = '';
                let authenticationResponse = null;
                try {
                    authenticationResponse = await Axios.post(`${config.backendAPI}/auth/login`, authenticationData, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                if (authenticationResponse.headers['set-cookie']) {
                    // Browser itself setup cookie and header is none
                    // In NodeJS we need do it manually
                    const cookies = authenticationResponse.headers['set-cookie'].join(';');
                    Axios.defaults.headers.common.Cookie = cookies;
                }

                token = authenticationResponse.data.key;
                store.set('token', token);
                Axios.defaults.headers.common.Authorization = `Token ${token}`;
            }

            async function logout() {
                try {
                    await Axios.post(`${config.backendAPI}/auth/logout`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                store.remove('token');
                Axios.defaults.headers.common.Authorization = '';
            }

            async function changePassword(oldPassword, newPassword1, newPassword2) {
                try {
                    const data = JSON.stringify({
                        old_password: oldPassword,
                        new_password1: newPassword1,
                        new_password2: newPassword2,
                    });
                    await Axios.post(`${config.backendAPI}/auth/password/change`, data, {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function requestPasswordReset(email) {
                try {
                    const data = JSON.stringify({
                        email,
                    });
                    await Axios.post(`${config.backendAPI}/auth/password/reset`, data, {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function resetPassword(newPassword1, newPassword2, uid, _token) {
                try {
                    const data = JSON.stringify({
                        new_password1: newPassword1,
                        new_password2: newPassword2,
                        uid,
                        token: _token,
                    });
                    await Axios.post(`${config.backendAPI}/auth/password/reset/confirm`, data, {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function authorized() {
                try {
                    await module.exports.users.self();
                } catch (serverError) {
                    if (serverError.code === 401) {
                        return false;
                    }

                    throw serverError;
                }

                return true;
            }

            async function serverRequest(url, data) {
                try {
                    return (
                        await Axios({
                            url,
                            ...data,
                        })
                    ).data;
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function searchProjectNames(search, limit) {
                const { backendAPI, proxy } = config;

                let response = null;
                try {
                    response = await Axios.get(
                        `${backendAPI}/projects?names_only=true&page=1&page_size=${limit}&search=${search}`,
                        {
                            proxy,
                        },
                    );
                } catch (errorData) {
                    throw generateError(errorData);
                }

                response.data.results.count = response.data.count;
                return response.data.results;
            }

            async function getProjects(filter = '') {
                const { backendAPI, proxy } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/projects?page_size=12&${filter}`, {
                        proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                response.data.results.count = response.data.count;
                return response.data.results;
            }

            async function saveProject(id, projectData) {
                const { backendAPI } = config;

                try {
                    await Axios.patch(`${backendAPI}/projects/${id}`, JSON.stringify(projectData), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function deleteProject(id) {
                const { backendAPI } = config;

                try {
                    await Axios.delete(`${backendAPI}/projects/${id}`);
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function createProject(projectSpec) {
                const { backendAPI } = config;

                try {
                    const response = await Axios.post(`${backendAPI}/projects`, JSON.stringify(projectSpec), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                    return response.data;
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function getTasks(filter = '') {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/tasks?page_size=10&${filter}`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                response.data.results.count = response.data.count;
                return response.data.results;
            }

            async function saveTask(id, taskData) {
                const { backendAPI } = config;

                try {
                    await Axios.patch(`${backendAPI}/tasks/${id}`, JSON.stringify(taskData), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function deleteTask(id) {
                const { backendAPI } = config;

                try {
                    await Axios.delete(`${backendAPI}/tasks/${id}`, {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function exportDataset(id, format) {
                const { backendAPI } = config;
                let url = `${backendAPI}/tasks/${id}/dataset?format=${format}`;

                return new Promise((resolve, reject) => {
                    async function request() {
                        try {
                            const response = await Axios.get(`${url}`, {
                                proxy: config.proxy,
                            });
                            if (response.status === 202) {
                                setTimeout(request, 3000);
                            } else {
                                url = `${url}&action=download`;
                                resolve(url);
                            }
                        } catch (errorData) {
                            reject(generateError(errorData));
                        }
                    }

                    setTimeout(request);
                });
            }

            async function createTask(taskSpec, taskDataSpec, onUpdate) {
                const { backendAPI } = config;

                async function wait(id) {
                    return new Promise((resolve, reject) => {
                        async function checkStatus() {
                            try {
                                const response = await Axios.get(`${backendAPI}/tasks/${id}/status`);
                                if (['Queued', 'Started'].includes(response.data.state)) {
                                    if (response.data.message !== '') {
                                        onUpdate(response.data.message);
                                    }
                                    setTimeout(checkStatus, 1000);
                                } else if (response.data.state === 'Finished') {
                                    resolve();
                                } else if (response.data.state === 'Failed') {
                                    // If request has been successful, but task hasn't been created
                                    // Then passed data is wrong and we can pass code 400
                                    const message = `
                                        Could not create the task on the server. ${response.data.message}.
                                    `;
                                    reject(new ServerError(message, 400));
                                } else {
                                    // If server has another status, it is unexpected
                                    // Therefore it is server error and we can pass code 500
                                    reject(
                                        new ServerError(
                                            `Unknown task state has been received: ${response.data.state}`,
                                            500,
                                        ),
                                    );
                                }
                            } catch (errorData) {
                                reject(generateError(errorData));
                            }
                        }

                        setTimeout(checkStatus, 1000);
                    });
                }

                const taskData = new FormData();
                for (const [key, value] of Object.entries(taskDataSpec)) {
                    if (Array.isArray(value)) {
                        value.forEach((element, idx) => {
                            taskData.append(`${key}[${idx}]`, element);
                        });
                    } else {
                        taskData.set(key, value);
                    }
                }

                let response = null;

                onUpdate('The task is being created on the server..');
                try {
                    response = await Axios.post(`${backendAPI}/tasks`, JSON.stringify(taskSpec), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                onUpdate('The data are being uploaded to the server..');
                try {
                    await Axios.post(`${backendAPI}/tasks/${response.data.id}/data`, taskData, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    try {
                        await deleteTask(response.data.id);
                    } catch (_) {
                        // ignore
                    }

                    throw generateError(errorData);
                }

                try {
                    await wait(response.data.id);
                } catch (createException) {
                    await deleteTask(response.data.id);
                    throw createException;
                }

                const createdTask = await getTasks(`?id=${response.id}`);
                return createdTask[0];
            }

            async function getJob(jobID) {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/jobs/${jobID}`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function getJobReviews(jobID) {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/jobs/${jobID}/reviews`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function createReview(data) {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.post(`${backendAPI}/reviews`, JSON.stringify(data), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function getJobIssues(jobID) {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/jobs/${jobID}/issues`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function createComment(data) {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.post(`${backendAPI}/comments`, JSON.stringify(data), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function updateIssue(issueID, data) {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.patch(`${backendAPI}/issues/${issueID}`, JSON.stringify(data), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function saveJob(id, jobData) {
                const { backendAPI } = config;

                try {
                    await Axios.patch(`${backendAPI}/jobs/${id}`, JSON.stringify(jobData), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function getUsers(filter = 'page_size=all') {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/users?${filter}`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data.results;
            }

            async function getSelf() {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/users/self`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            async function getPreview(tid) {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/tasks/${tid}/data?type=preview`, {
                        proxy: config.proxy,
                        responseType: 'blob',
                    });
                } catch (errorData) {
                    const code = errorData.response ? errorData.response.status : errorData.code;
                    throw new ServerError(`Could not get preview frame for the task ${tid} from the server`, code);
                }

                return response.data;
            }

            async function getData(tid, chunk) {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await workerAxios.get(
                        `${backendAPI}/tasks/${tid}/data?type=chunk&number=${chunk}&quality=compressed`,
                        {
                            proxy: config.proxy,
                            responseType: 'arraybuffer',
                        },
                    );
                } catch (errorData) {
                    throw generateError({
                        ...errorData,
                        message: '',
                        response: {
                            ...errorData.response,
                            data: String.fromCharCode.apply(null, new Uint8Array(errorData.response.data)),
                        },
                    });
                }

                return response;
            }

            async function getMeta(tid) {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/tasks/${tid}/data/meta`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            // Session is 'task' or 'job'
            async function getAnnotations(session, id) {
                const { backendAPI } = config;

                let response = null;
                try {
                    response = await Axios.get(`${backendAPI}/${session}s/${id}/annotations`, {
                        proxy: config.proxy,
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            // Session is 'task' or 'job'
            async function updateAnnotations(session, id, data, action) {
                const { backendAPI } = config;
                let requestFunc = null;
                let url = null;
                if (action.toUpperCase() === 'PUT') {
                    requestFunc = Axios.put.bind(Axios);
                    url = `${backendAPI}/${session}s/${id}/annotations`;
                } else {
                    requestFunc = Axios.patch.bind(Axios);
                    url = `${backendAPI}/${session}s/${id}/annotations?action=${action}`;
                }

                let response = null;
                try {
                    response = await requestFunc(url, JSON.stringify(data), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }

                return response.data;
            }

            // Session is 'task' or 'job'
            async function uploadAnnotations(session, id, file, format) {
                const { backendAPI } = config;

                let annotationData = new FormData();
                annotationData.append('annotation_file', file);

                return new Promise((resolve, reject) => {
                    async function request() {
                        try {
                            const response = await Axios.put(
                                `${backendAPI}/${session}s/${id}/annotations?format=${format}`,
                                annotationData,
                                {
                                    proxy: config.proxy,
                                },
                            );
                            if (response.status === 202) {
                                annotationData = new FormData();
                                setTimeout(request, 3000);
                            } else {
                                resolve();
                            }
                        } catch (errorData) {
                            reject(generateError(errorData));
                        }
                    }

                    setTimeout(request);
                });
            }

            // Session is 'task' or 'job'
            async function dumpAnnotations(id, name, format) {
                const { backendAPI } = config;
                const baseURL = `${backendAPI}/tasks/${id}/annotations`;
                let query = `format=${encodeURIComponent(format)}`;
                if (name) {
                    const filename = name.replace(/\//g, '_');
                    query += `&filename=${encodeURIComponent(filename)}`;
                }
                let url = `${baseURL}?${query}`;

                return new Promise((resolve, reject) => {
                    async function request() {
                        Axios.get(`${url}`, {
                            proxy: config.proxy,
                        })
                            .then((response) => {
                                if (response.status === 202) {
                                    setTimeout(request, 3000);
                                } else {
                                    query = `${query}&action=download`;
                                    url = `${baseURL}?${query}`;
                                    resolve(url);
                                }
                            })
                            .catch((errorData) => {
                                reject(generateError(errorData));
                            });
                    }

                    setTimeout(request);
                });
            }

            async function saveLogs(logs) {
                const { backendAPI } = config;

                try {
                    await Axios.post(`${backendAPI}/server/logs`, JSON.stringify(logs), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function getLambdaFunctions() {
                const { backendAPI } = config;

                try {
                    const response = await Axios.get(`${backendAPI}/lambda/functions`, {
                        proxy: config.proxy,
                    });
                    return response.data;
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function runLambdaRequest(body) {
                const { backendAPI } = config;

                try {
                    const response = await Axios.post(`${backendAPI}/lambda/requests`, JSON.stringify(body), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });

                    return response.data;
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function callLambdaFunction(funId, body) {
                const { backendAPI } = config;

                try {
                    const response = await Axios.post(`${backendAPI}/lambda/functions/${funId}`, JSON.stringify(body), {
                        proxy: config.proxy,
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });

                    return response.data;
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function getLambdaRequests() {
                const { backendAPI } = config;

                try {
                    const response = await Axios.get(`${backendAPI}/lambda/requests`, {
                        proxy: config.proxy,
                    });

                    return response.data;
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function getRequestStatus(requestID) {
                const { backendAPI } = config;

                try {
                    const response = await Axios.get(`${backendAPI}/lambda/requests/${requestID}`, {
                        proxy: config.proxy,
                    });
                    return response.data;
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function cancelLambdaRequest(requestId) {
                const { backendAPI } = config;

                try {
                    await Axios.delete(`${backendAPI}/lambda/requests/${requestId}`, {
                        method: 'DELETE',
                    });
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            async function installedApps() {
                const { backendAPI } = config;
                try {
                    const response = await Axios.get(`${backendAPI}/server/plugins`, {
                        proxy: config.proxy,
                    });
                    return response.data;
                } catch (errorData) {
                    throw generateError(errorData);
                }
            }

            Object.defineProperties(
                this,
                Object.freeze({
                    server: {
                        value: Object.freeze({
                            about,
                            share,
                            formats,
                            exception,
                            login,
                            logout,
                            changePassword,
                            requestPasswordReset,
                            resetPassword,
                            authorized,
                            register,
                            request: serverRequest,
                            userAgreements,
                            installedApps,
                        }),
                        writable: false,
                    },

                    projects: {
                        value: Object.freeze({
                            get: getProjects,
                            searchNames: searchProjectNames,
                            save: saveProject,
                            create: createProject,
                            delete: deleteProject,
                        }),
                        writable: false,
                    },

                    tasks: {
                        value: Object.freeze({
                            getTasks,
                            saveTask,
                            createTask,
                            deleteTask,
                            exportDataset,
                        }),
                        writable: false,
                    },

                    jobs: {
                        value: Object.freeze({
                            get: getJob,
                            save: saveJob,
                            issues: getJobIssues,
                            reviews: {
                                get: getJobReviews,
                                create: createReview,
                            },
                        }),
                        writable: false,
                    },

                    users: {
                        value: Object.freeze({
                            get: getUsers,
                            self: getSelf,
                        }),
                        writable: false,
                    },

                    frames: {
                        value: Object.freeze({
                            getData,
                            getMeta,
                            getPreview,
                        }),
                        writable: false,
                    },

                    annotations: {
                        value: Object.freeze({
                            updateAnnotations,
                            getAnnotations,
                            dumpAnnotations,
                            uploadAnnotations,
                        }),
                        writable: false,
                    },

                    logs: {
                        value: Object.freeze({
                            save: saveLogs,
                        }),
                        writable: false,
                    },

                    lambda: {
                        value: Object.freeze({
                            list: getLambdaFunctions,
                            status: getRequestStatus,
                            requests: getLambdaRequests,
                            run: runLambdaRequest,
                            call: callLambdaFunction,
                            cancel: cancelLambdaRequest,
                        }),
                        writable: false,
                    },

                    issues: {
                        value: Object.freeze({
                            update: updateIssue,
                        }),
                        writable: false,
                    },

                    comments: {
                        value: Object.freeze({
                            create: createComment,
                        }),
                        writable: false,
                    },
                }),
            );
        }
    }

    const serverProxy = new ServerProxy();
    module.exports = serverProxy;
})();
