import * as color from '@heroku/heroku-cli-util/color';
import { ux } from '@oclif/core/ux';
import { EventSource } from 'eventsource';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { getGenerationByAppId } from '../apps/generation.js';
import colorize from './colorize.js';
export class LogDisplayer {
    heroku;
    constructor(heroku) {
        this.heroku = heroku;
    }
    createEventSourceInstance(url, options) {
        return new EventSource(url, options);
    }
    async display(options) {
        this.setupProcessHandlers();
        const firApp = (await this.getGenerationByAppId(options)) === 'fir';
        const isTail = firApp || options.tail;
        const requestBodyParameters = this.buildRequestBodyParameters(firApp, options);
        let recreateLogSession = false;
        do {
            const logSession = await this.createLogSession(requestBodyParameters, options.app);
            try {
                await this.readLogs(logSession.logplex_url, isTail, firApp ? Number(process.env.HEROKU_LOG_STREAM_TIMEOUT || '15') * 60 * 1000 : undefined);
            }
            catch (error) {
                const { message } = error;
                if (message === 'Fir log stream timeout')
                    recreateLogSession = true;
                else
                    ux.error(message, { exit: 1 });
            }
        } while (recreateLogSession);
    }
    buildRequestBodyParameters(firApp, options) {
        const requestBodyParameters = {
            source: options.source,
        };
        if (firApp) {
            process.stderr.write(color.info('Fetching logs...\n\n'));
            Object.assign(requestBodyParameters, {
                dyno: options.dyno,
                type: options.type,
            });
        }
        else {
            Object.assign(requestBodyParameters, {
                dyno: options.dyno || options.type,
                lines: options.lines,
                tail: options.tail,
            });
        }
        return requestBodyParameters;
    }
    async createLogSession(requestBodyParameters, app) {
        const { body: logSession } = await this.heroku.post(`/apps/${app}/log-sessions`, {
            body: requestBodyParameters,
            headers: { Accept: 'application/vnd.heroku+json; version=3.sdk' },
        });
        return logSession;
    }
    async getGenerationByAppId(options) {
        const generation = await getGenerationByAppId(options.app, this.heroku);
        return generation || '';
    }
    readLogs(logplexURL, isTail, recreateSessionTimeout) {
        return new Promise((resolve, reject) => {
            const userAgent = process.env.HEROKU_DEBUG_USER_AGENT || 'heroku-run';
            const proxy = process.env.https_proxy || process.env.HTTPS_PROXY;
            // Custom fetch function to handle headers and proxy
            const customFetch = async (input, init) => {
                const headers = new Headers(init?.headers);
                headers.set('User-Agent', userAgent);
                const fetchOptions = {
                    ...init,
                    headers,
                };
                // If proxy is set, use https-proxy-agent
                if (proxy) {
                    const proxyAgent = new HttpsProxyAgent(proxy);
                    fetchOptions.agent = proxyAgent;
                }
                return fetch(input, fetchOptions);
            };
            const es = this.createEventSourceInstance(logplexURL, {
                fetch: customFetch,
            });
            es.addEventListener('error', (err) => {
                // The new eventsource package provides message and code properties on errors
                const errorEvent = err;
                if (errorEvent && (errorEvent.code || errorEvent.message)) {
                    const msg = (isTail && (errorEvent.code === 404 || errorEvent.code === 403))
                        ? 'Log stream timed out. Please try again.'
                        : `Logs eventsource failed with: ${errorEvent.code}${errorEvent.message ? ` ${errorEvent.message}` : ''}`;
                    reject(new Error(msg));
                    es.close();
                }
                if (!isTail) {
                    resolve();
                    es.close();
                }
                // should only land here if --tail and no error status or message
            });
            es.addEventListener('message', (e) => {
                e.data.trim().split(/\n+/).forEach((line) => {
                    ux.stdout(colorize(line));
                });
            });
            if (isTail && recreateSessionTimeout) {
                setTimeout(() => {
                    reject(new Error('Fir log stream timeout'));
                    es.close();
                }, recreateSessionTimeout);
            }
        });
    }
    setupProcessHandlers() {
        process.stdout.on('error', err => {
            if (err.code === 'EPIPE') {
                // eslint-disable-next-line n/no-process-exit
                process.exit(0);
            }
            else {
                ux.error(err.stack, { exit: 1 });
            }
        });
    }
}
// Default export for backward compatibility
export default async function logDisplayer(heroku, options) {
    const displayer = new LogDisplayer(heroku);
    await displayer.display(options);
}
