import superagent from 'superagent';
import Agent, { HttpsAgent } from 'agentkeepalive';
import http from 'http';
import https from 'https';
import { URL } from 'url';
import { pipeline } from 'stream';
import logger from '../utils/logger.js';
import sanitise from '../utils/sanitisedError.js';

class RestClient {
    name;
    config;
    agent;
    constructor(name, config) {
        this.name = name;
        this.config = config;
        this.agent = config.url.startsWith('https') ? new HttpsAgent(config.agent) : new Agent(config.agent);
    }
    apiUrl() {
        return this.config.url;
    }
    timeoutConfig() {
        return this.config.agent.timeout;
    }
    async get(request) {
        return this.getWithHeaders(request).then((result) => result.data);
    }
    async getStream({ path = '', query = {}, headers = {}, token }, res) {
        const url = new URL(`${this.apiUrl()}${path}`);
        Object.entries(query).forEach(([key, value]) => {
            if (value === undefined)
                return;
            if (Array.isArray(value)) {
                value.forEach((v) => url.searchParams.append(key, String(v)));
            }
            else {
                url.searchParams.append(key, String(value));
            }
        });
        const client = url.protocol === 'https:' ? https : http;
        const req = client.request(url, {
            method: 'GET',
            headers: {
                ...headers,
                Authorization: token ? `Bearer ${token}` : undefined,
            },
            agent: this.agent,
        }, (upstream) => {
            // Forward status
            res.status(upstream.statusCode || 500);
            // Forward headers
            Object.entries(upstream.headers).forEach(([key, value]) => {
                if (value !== undefined) {
                    res.setHeader(key, value);
                }
            });
            res.flushHeaders();
            res.on('close', () => {
                req.destroy();
            });
            pipeline(upstream, res, (err) => {
                if (err) {
                    res.destroy(err);
                }
            });
        });
        req.setTimeout(this.timeoutConfig(), () => {
            req.destroy(new Error('Upstream request timed out.'));
        });
        req.on('error', (err) => {
            logger.warn({ err }, `Error streaming from ${this.name}, path: '${path}'`);
            if (!res.headersSent) {
                res.status(502).end('Download request failed');
            }
            else {
                res.destroy(err);
            }
        });
        req.end();
    }
    async requestWithBody(method, { path, query = {}, headers = {}, responseType = '', data = {}, raw = false, retry = false }, token) {
        logger.info(`${this.name} ${method.toUpperCase()}: ${path}`);
        logger.info(`info about request: ${method} | ${path} | ${JSON.stringify(data)} | ${query}`);
        try {
            const result = await superagent[method](`${this.apiUrl()}${path}`)
                .query(query)
                .send(data)
                .agent(this.agent)
                .retry(2, (err) => {
                if (retry === false) {
                    return false;
                }
                if (err)
                    logger.info(`Retry handler found API error with ${err.code} ${err.message}`);
                return undefined; // retry handler only for logging retries, not to influence retry logic
            })
                .auth(token, { type: 'bearer' })
                .set(headers)
                .responseType(responseType)
                .timeout(this.timeoutConfig());
            return raw ? result : result.body;
        }
        catch (error) {
            if (!(error instanceof Error)) {
                throw error;
            }
            const sanitisedError = sanitise(error);
            logger.warn({ ...sanitisedError }, `Error calling ${this.name}, path: '${path}', verb: '${method.toUpperCase()}'`);
            throw sanitisedError;
        }
    }
    async post(request, token) {
        return this.requestWithBody('post', request, token);
    }
    async getWithHeaders({ path = '', query = {}, headers = {}, responseType = '', raw = false, token, }) {
        const loggerData = {
            path: `${this.config.url}${path}`,
            query,
        };
        logger.info(`${this.name}: ${JSON.stringify(loggerData, null, 2)}`);
        try {
            const result = await superagent
                .get(`${this.apiUrl()}${path}`)
                .agent(this.agent)
                .retry(2, (err) => {
                if (err)
                    logger.info(`Retry handler found API error with ${err.code} ${err.message}`);
                return undefined; // retry handler only for logging retries, not to influence retry logic
            })
                .query(query)
                .auth(token, { type: 'bearer' })
                .set(headers)
                .responseType(responseType)
                .timeout(this.timeoutConfig());
            return {
                data: raw ? result : result.body,
                headers: result.headers,
            };
        }
        catch (error) {
            const sanitisedError = sanitise(error);
            logger.warn({ ...sanitisedError, query }, `Error calling ${this.name}, path: '${path}', verb: 'GET'`);
            throw sanitisedError;
        }
    }
    async deleteWithHeaders({ path = '', query = {}, headers = {}, responseType = '', raw = false, token, }) {
        const loggerData = {
            path: `${this.config.url}${path}`,
            query,
        };
        logger.info(`${this.name}: ${JSON.stringify(loggerData, null, 2)}`);
        try {
            const result = await superagent
                .delete(`${this.apiUrl()}${path}`)
                .agent(this.agent)
                .retry(2, (err) => {
                if (err)
                    logger.info(`Retry handler found API error with ${err.code} ${err.message}`);
                return undefined; // retry handler only for logging retries, not to influence retry logic
            })
                .query(query)
                .auth(token, { type: 'bearer' })
                .set(headers)
                .responseType(responseType)
                .timeout(this.timeoutConfig());
            return {
                data: raw ? result : result.body,
                headers: result.headers,
            };
        }
        catch (error) {
            const sanitisedError = sanitise(error);
            logger.warn({ ...sanitisedError, query }, `Error calling ${this.name}, path: '${path}', verb: 'GET'`);
            throw sanitisedError;
        }
    }
    async delete(request) {
        return this.deleteWithHeaders(request).then((result) => result.data);
    }
}

export { RestClient, RestClient as default };
//# sourceMappingURL=restClient.js.map
