"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExportRequestProvider = void 0;
const mablApi_1 = require("../mablApi");
const loggingProvider_1 = require("./logging/loggingProvider");
const basicApiClient_1 = require("../api/basicApiClient");
const fs = require('fs');
const ora = require('ora');
const chalk = require('chalk');
class ExportRequestProvider {
    constructor(apiClient, targetEntityId, targetEntityType, targetContentTypes, outputFilepath) {
        this.pollingTimeoutMilliseconds = 10 * 60 * 1000;
        this.pollingIntervalMilliseconds = 1000;
        this.exportStartEpochMilliseconds = 0;
        this.continuationStatuses = [
            mablApi_1.ExportRequest.StatusEnum.Queued,
            mablApi_1.ExportRequest.StatusEnum.Running,
        ];
        this.apiClient = apiClient;
        this.targetEntityId = targetEntityId;
        this.targetEntityType = targetEntityType;
        this.contentTypes = targetContentTypes;
        this.outputFilepath = outputFilepath;
    }
    isExportIncomplete() {
        if (this.latestRequest?.status) {
            return this.continuationStatuses.includes(this.latestRequest.status);
        }
        return false;
    }
    isExportCompleted() {
        return !this.isExportIncomplete();
    }
    static mapStatusToColor(status) {
        switch (status) {
            case mablApi_1.ExportRequest.StatusEnum.Queued:
                return 'gray';
            case mablApi_1.ExportRequest.StatusEnum.Running:
                return 'yellow';
            case mablApi_1.ExportRequest.StatusEnum.Completed:
                return 'green';
            case mablApi_1.ExportRequest.StatusEnum.Failed:
            case mablApi_1.ExportRequest.StatusEnum.Terminated:
                return 'red';
            default:
                return 'orange';
        }
    }
    async pollForStatusUpdate(lastRequestId) {
        const newLatestRequest = await this.apiClient.getExportRequest(lastRequestId);
        this.latestRequest = newLatestRequest;
        const statusColor = ExportRequestProvider.mapStatusToColor(newLatestRequest.status);
        const updatedMessage = `Processing export [${chalk.magenta.bold(lastRequestId)}] status: [${chalk
            .keyword(statusColor)
            .bold(newLatestRequest.status.toString())}]`;
        updateSpinnerText(updatedMessage, this.exportStatusSpinner);
        return this.latestRequest;
    }
    async awaitCompletion() {
        if (!this.latestRequest?.id) {
            throw new Error('CreateExportRequest is undefined');
        }
        await this.pollForStatusUpdate(this.latestRequest.id);
        const notTimedOut = Date.now() - this.exportStartEpochMilliseconds <
            this.pollingTimeoutMilliseconds;
        if (notTimedOut && this.isExportIncomplete()) {
            return new Promise((resolve, reject) => {
                setTimeout(() => this.awaitCompletion()
                    .then(() => resolve())
                    .catch((error) => reject(error)), this.pollingIntervalMilliseconds);
            });
        }
    }
    async processExport() {
        try {
            const testRun = await this.apiClient.getTestRun(this.targetEntityId);
            if (testRun.test_type === mablApi_1.TestTypeEnum.Api) {
                loggingProvider_1.logger.error(chalk.red(`Test run export not supported for API test runs: ${this.targetEntityId}`));
                process.exit(1);
            }
            loggingProvider_1.logger.info(`Export ${this.targetEntityType} [${chalk.magenta.bold(this.targetEntityId)}]`);
            this.createExportSpinner = ora({
                prefixText: ' ',
                text: 'Creating export request',
            }).start();
            const targetContent = this
                .contentTypes;
            this.latestRequest = await this.apiClient.createExportRequest(this.targetEntityId, targetContent);
            const updateMessage = `Creating export request [${chalk.magenta.bold(this.latestRequest.id)}].  Types to export: ${chalk.magenta.bold(this.contentTypes.join(','))}`;
            updateSpinnerSuccess(this.createExportSpinner, updateMessage);
            this.exportStartEpochMilliseconds = Date.now();
            this.latestRequest = await this.apiClient.getExportRequest(this.latestRequest.id);
            this.exportStatusSpinner = ora({
                prefixText: ' ',
                text: 'Processing export',
            }).start();
            await this.awaitCompletion();
            if (!this.isExportCompleted()) {
                loggingProvider_1.logger.error(chalk.red(`Export request [${this.latestRequest
                    .id}] timed out. Please try again.`));
                process.exit(1);
            }
            switch (this.latestRequest.status) {
                case mablApi_1.ExportRequest.StatusEnum.Failed:
                case mablApi_1.ExportRequest.StatusEnum.Terminated:
                    updateSpinnerFail(this.createExportSpinner);
                    loggingProvider_1.logger.error(chalk.red(`Export request [${this.latestRequest.id}] ${this.latestRequest.status
                        .toString()
                        .toUpperCase()}. Please try again.`));
                    process.exit(1);
                    break;
                case mablApi_1.ExportRequest.StatusEnum.Completed:
                default:
                    updateSpinnerSuccess(this.exportStatusSpinner);
            }
            const downloadUrl = this.latestRequest.output_signed_url;
            if (!downloadUrl) {
                throw new Error(`No download url provided for ExportRequest [${this.latestRequest.id}]`);
            }
            const outputFilename = this.outputFilepath || getDownloadFilename(downloadUrl);
            this.exportDownloadSpinner = ora({
                prefixText: ' ',
                text: `Downloading export artifact to [${chalk.magenta.bold(outputFilename)}]`,
            }).start();
            const client = await basicApiClient_1.BasicApiClient.create();
            const responseData = await client.makeGetRequest(downloadUrl, undefined, {
                headers: {
                    Accept: 'application/zip',
                },
                responseType: 'stream',
            });
            const writer = fs.createWriteStream(outputFilename);
            await new Promise((resolve, reject) => {
                try {
                    responseData.pipe(writer);
                    writer.on('error', (err) => {
                        writer.close();
                        reject(err);
                    });
                    writer.on('finish', () => {
                        writer.close();
                        resolve();
                    });
                }
                catch (exception) {
                    reject(exception);
                }
            });
            updateSpinnerSuccessStop(this.exportDownloadSpinner);
            return outputFilename;
        }
        finally {
            [
                this.createExportSpinner,
                this.exportStatusSpinner,
                this.exportDownloadSpinner,
            ]
                .forEach((spinner) => {
                if (spinner?.isSpinning) {
                    spinner.fail();
                }
            });
        }
    }
}
exports.ExportRequestProvider = ExportRequestProvider;
const filenameExtractorRegex = /\/([^\/]+)\?/;
function getDownloadFilename(downloadUrl) {
    const urlParts = downloadUrl.match(filenameExtractorRegex);
    if (urlParts && urlParts.length >= 2) {
        return urlParts[1];
    }
    throw new Error('Output URL undefined');
}
function updateSpinnerSuccess(spinner, message) {
    if (spinner) {
        spinner.succeed(message);
    }
}
function updateSpinnerSuccessStop(spinner, message) {
    if (spinner) {
        spinner.stop().succeed(message);
    }
}
function updateSpinnerFail(spinner, message) {
    if (spinner) {
        spinner.fail(message);
    }
}
function updateSpinnerText(message, spinner) {
    if (spinner) {
        spinner.text = message;
    }
}
