/* eslint-disable no-console, complexity */
// higher-order function, возвращающая переданный fetch, обёрнутый логированием;
// сигнатура withLogs(fetch) и fetch одинаковая для взаимозаменяемости обоих вариантов
const { URL } = require('url');
const { Readable } = require('stream');
const fromEntries = require('./fromEntries');

const MAX_LOGGED_BODY_SIZE = 50 * 1024;

function toStream(buffer) {
    let stream = new Readable();

    stream.push(buffer);
    stream.push(null);

    return stream;
}

let withLogs = fetch => (url, options = {}) => {
    let { method = 'GET' } = options;
    let urlObject = new URL(url);

    let request = {
        url: `${urlObject.origin}${urlObject.pathname}`,
        query: fromEntries(urlObject.searchParams.entries()),
        ...options,
    };

    console.log(`${method} ${request.url} [Started]`, { request });

    let t0 = process.hrtime();

    return fetch(url, options)
        .then(res => {
            let [sec, nanosec] = process.hrtime(t0);
            let duration = sec * 1e3 + nanosec / 1e6;

            res.headers.set('x-duration', duration);

            let { ok, status, statusText, headers } = res;

            let message = `${method} ${request.url} - ${status} ${statusText}`;
            let response = { ok, status, statusText, duration };

            return res.buffer().then(buf => {
                response.length = buf.length;

                if (buf.length < MAX_LOGGED_BODY_SIZE) {
                    let text = buf.toString();

                    try {
                        let json = text && JSON.parse(text);

                        if (!ok && json && json.code) {
                            message += ` - ${json.code}`;
                        }

                        response.body = json;
                    } catch (e) {
                        response.body = text;
                    }
                }

                console[ok ? 'log' : 'error'](message, { request, response });

                return {
                    ok,
                    status,
                    statusText,
                    headers,
                    body: toStream(buf),
                    buffer: () => Promise.resolve(buf),
                    text: () => Promise.resolve(buf.toString()),
                    json: () => Promise.resolve(JSON.parse(buf.toString())),
                };
            });
        });
};

module.exports = withLogs;
