"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const env_1 = require("@mablhq-internal/runtime/env/env");
const Timers = __importStar(require("timers"));
const pluralize_1 = __importDefault(require("pluralize"));
const inquirer = __importStar(require("inquirer"));
const mablApiClientFactory_1 = require("@mablhq-internal/runtime/api/mablApiClientFactory");
const util_1 = require("../../../commandUtil/util");
const cliUtil_1 = require("@mablhq-internal/runtime/core/cliUtil");
const mablApi_1 = require("@mablhq-internal/runtime/mablApi");
const index_1 = require("@mablhq-internal/runtime/proxy/index");
const RichPromise_1 = __importDefault(require("@mablhq-internal/runtime/util/RichPromise"));
const pureUtil_1 = require("@mablhq-internal/runtime/util/pureUtil");
const constants_1 = require("@mablhq-internal/runtime/core/constants");
const constants_2 = require("../../../constants");
const authenticationProvider_1 = require("@mablhq-internal/runtime/providers/authenticationProvider");
const asyncUtil_1 = require("@mablhq-internal/runtime/util/asyncUtil");
const loggingProvider_1 = require("@mablhq-internal/runtime/providers/logging/loggingProvider");
const chromiumBrowserEngine_1 = require("@mablhq-internal/runtime/browserEngines/chromiumBrowserEngine");
const { MablTestRunner, MablTestsRunner } = require('@mablhq-internal/runtime/execution');
const DEFAULT_ASYNC_TIMEOUT_MILLIS = 120_000;
var PostImportActions;
(function (PostImportActions) {
    PostImportActions["Discard"] = "Discard the test";
    PostImportActions["Run"] = "Run the test locally";
    PostImportActions["Save"] = "Save the test";
    PostImportActions["View"] = "View the test description";
})(PostImportActions || (PostImportActions = {}));
exports.command = 'selenium';
exports.describe = 'Import an existing Selenium-based test';
exports.builder = (yargs) => {
    yargs
        .option(constants_2.CommandArgAuto, {
        default: false,
        describe: 'Automatically save imported test when complete',
        required: false,
        requiresArg: false,
        type: 'boolean',
    })
        .option(constants_2.CommandArgDebug, {
        default: false,
        describe: 'Enable debug mode',
        hidden: true,
        required: false,
        requiresArg: false,
        type: 'boolean',
    })
        .option(constants_2.CommandArgMulti, {
        default: false,
        describe: 'Import multiple tests at once',
        required: false,
        requiresArg: false,
        type: 'boolean',
    })
        .option(constants_2.CommandArgName, {
        alias: constants_2.CommandArgAliases.Name,
        describe: 'The name to give this test in mabl',
        nargs: 1,
        required: false,
        type: 'string',
    })
        .option(constants_1.CommandArgPortNumber, {
        default: 8889,
        describe: 'The port on which the Selenium proxy should listen',
        min: 1,
        max: 65535,
        nargs: 1,
        required: false,
        type: 'number',
    })
        .option(constants_1.CommandArgWorkspaceId, {
        alias: constants_2.CommandArgAliases.WorkspaceId,
        describe: 'Workspace into which tests should be imported',
        nargs: 1,
        required: false,
        requiresArg: true,
        type: 'string',
    });
};
exports.handler = (0, cliUtil_1.failWrapper)(importTest);
async function importTest(parsed) {
    const workspaceId = await (0, util_1.getWorkspaceId)(parsed);
    const importMultipleTests = parsed.multi;
    const apiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClient();
    const name = await getTestName(parsed.name);
    const importedTests = await importTests(parsed.port, parsed.debug, importMultipleTests);
    if (!importedTests.length) {
        return Promise.reject(new Error(`No ${importMultipleTests ? 'tests' : 'steps'} were imported.`));
    }
    if (importedTests.length > 1) {
        loggingProvider_1.logger.info(`Imported ${importedTests.length} tests.\n`);
    }
    else {
        loggingProvider_1.logger.info(`Imported ${(0, pluralize_1.default)('step', importedTests[0].length, true)}.\n`);
    }
    return handlePostImportActions(workspaceId, name, apiClient, importedTests, parsed.autoSave);
}
function getTestName(name) {
    const nameValidator = (value) => !value?.length ? 'Name must not be empty' : true;
    return name
        ? Promise.resolve(name)
        : inquirer
            .prompt([
            {
                type: 'input',
                name: 'name',
                message: 'Enter a name for this test',
                validate: nameValidator,
            },
        ])
            .then((answers) => answers.name);
}
async function importTests(port, debug, importMultipleTests) {
    const importedTests = [];
    const importCancelPromise = new RichPromise_1.default();
    const sessionClosedPromise = new RichPromise_1.default();
    const seleniumProxy = new index_1.SeleniumProxy(port, debug, () => {
        const steps = seleniumProxy.reset();
        if (steps.length) {
            importedTests.push(steps);
            if (importMultipleTests) {
                loggingProvider_1.logger.info(`Detected end of test ${importedTests.length} with ${(0, pluralize_1.default)('step', steps.length, true)}.`);
                loggingProvider_1.logger.info('Press CTRL+C to end multiple test import.');
            }
        }
        if (sessionClosedPromise.isPending()) {
            sessionClosedPromise.resolve();
        }
    });
    process.once('SIGINT', () => {
        loggingProvider_1.logger.logNewLine();
        importCancelPromise.resolve();
    });
    await seleniumProxy.start();
    loggingProvider_1.logger.info(`Configure your test to use a selenium proxy at localhost:${port} and run the test now.`);
    if (importMultipleTests) {
        loggingProvider_1.logger.info('Press CTRL+C to end multiple test import.');
        await importCancelPromise;
    }
    else {
        loggingProvider_1.logger.info('Import will end automatically when the Selenium session is closed or CTRL+C is pressed');
        await Promise.race([importCancelPromise, sessionClosedPromise]);
    }
    await new Promise((resolve) => {
        Timers.setImmediate(async () => {
            try {
                await seleniumProxy.stop();
            }
            catch (error) {
                if (debug) {
                    loggingProvider_1.logger.error(`Error stopping selenium proxy: ${error}`);
                }
            }
            resolve();
        });
    });
    return importedTests;
}
async function handlePostImportActions(workspaceId, name, apiClient, importedTests, autoSave) {
    let nextAction;
    do {
        nextAction = await getNextAction(autoSave);
        loggingProvider_1.logger.logNewLine();
        switch (nextAction) {
            case PostImportActions.Save:
                return saveTests(workspaceId, name, apiClient, importedTests).then(() => {
                    loggingProvider_1.logger.info('Import complete.');
                });
            case PostImportActions.View:
                displayTestDescriptions(importedTests);
                break;
            case PostImportActions.Run:
                await runTests(workspaceId, name, apiClient, importedTests);
                break;
            case PostImportActions.Discard:
                loggingProvider_1.logger.info('Exiting without saving.');
                return;
        }
        loggingProvider_1.logger.logNewLine();
    } while ([PostImportActions.View, PostImportActions.Run].includes(nextAction));
}
async function getNextAction(autoSave) {
    if (autoSave) {
        return PostImportActions.Save;
    }
    const answer = await inquirer.prompt([
        {
            type: 'list',
            name: 'action',
            hint: false,
            message: 'What do you want to do next?',
            choices: Object.values(PostImportActions),
        },
    ]);
    return answer.action;
}
function saveTests(workspaceId, name, apiClient, importedTests) {
    loggingProvider_1.logger.info(`Saving ${(0, pluralize_1.default)('test', importedTests.length, true)}...`);
    const savePromises = importedTests.map((steps, index) => apiClient
        .createFlow(stepsToFlow(workspaceId, steps))
        .then((flow) => apiClient.createJourney(flowToJourney(importedTests.length === 1
        ? name
        : `${name} (${index + 1} of ${importedTests.length})`, flow)))
        .then((journey) => {
        loggingProvider_1.logger.info(`Saved test ${journey.invariant_id} : ${env_1.BASE_APP_URL}/workspaces/${journey.organization_id}/train/tests/${journey.invariant_id}/current`);
        return journey;
    }));
    return Promise.all(savePromises);
}
function displayTestDescriptions(importedTests) {
    importedTests.forEach((steps, index) => {
        if (importedTests.length > 1) {
            loggingProvider_1.logger.info(`Test ${index + 1} of ${importedTests.length}:`);
        }
        loggingProvider_1.logger.info(steps.map((step) => step.description).join('\n'));
        loggingProvider_1.logger.logNewLine();
    });
}
async function runTests(workspaceId, name, apiClient, importedTests) {
    const flows = importedTests.map((steps) => stepsToFlow(workspaceId, steps));
    const firstUrl = flows.find((flow) => flow.url)?.url;
    const browserPath = await new chromiumBrowserEngine_1.ChromiumBrowserEngine().findBrowserExecutable();
    for (let flowIndex = 0; flowIndex < flows.length; flowIndex++) {
        loggingProvider_1.logger.info(`Running test ${flowIndex + 1} of ${flows.length}.`);
        const flow = flows[flowIndex];
        const journey = {
            organization_id: workspaceId,
            name,
            flows: [],
            url: flow.url ?? firstUrl,
        };
        const testRunner = await createTestRunner(apiClient, journey, flow, journey.url, workspaceId, browserPath);
        const results = await testRunner.run();
        loggingProvider_1.logger.info(`Test result: ${results.status}`);
    }
}
function stepsToFlow(workspaceId, steps) {
    const prototype = {
        import_source_id: mablApi_1.ImportSourceId.SeleniumNodeProxy,
        import_source_version: (0, pureUtil_1.getCliVersion)(),
        flow_type: mablApi_1.FlowTypeEnum.Mablscript,
        organization_id: workspaceId,
        reusable: false,
        selectors: steps
            .map((step) => step.selector)
            .map((selector) => selector?.toMablscriptSelector())
            .filter((selector) => selector)
            .map((selector) => selector),
        script: steps.map((step) => step.mablscript).join('\n'),
        script_description: steps.map((step) => step.description).join('\n'),
        url: steps.map((step) => step.url).find((url) => url),
    };
    return prototype;
}
function flowToJourney(name, flow) {
    const prototype = {
        organization_id: flow.organization_id,
        name,
        flows: [flow.id],
        url: flow.url,
    };
    return prototype;
}
async function createTestRunner(apiClient, test, flow, url, workspaceId, browserPath) {
    const authConfig = await (0, asyncUtil_1.promiseWithTimeout)(new authenticationProvider_1.AuthenticationProvider().getAuthConfigWithAutoRenew(), DEFAULT_ASYNC_TIMEOUT_MILLIS, 'Get auth config with auto renew');
    await (0, asyncUtil_1.promiseWithTimeout)(MablTestsRunner.validateAuth(apiClient, authConfig), DEFAULT_ASYNC_TIMEOUT_MILLIS, 'Validate auth');
    const runner = new MablTestRunner({
        mablApiClient: apiClient,
        testRunConfig: { imported: true, url, workspaceId },
    });
    await runner.initializeTestRunner(test, [flow], 'master', apiClient, browserPath, false, { url });
    return runner;
}
