"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeRunCloudTest = executeRunCloudTest;
const mablApiClientFactory_1 = require("@mablhq-internal/runtime/api/mablApiClientFactory");
const mablApi_1 = require("@mablhq-internal/runtime/mablApi");
const constants_1 = require("@mablhq-internal/runtime/core/constants");
const constants_2 = require("../../constants");
const browserTypes_1 = require("@mablhq-internal/runtime/core/browserTypes");
const util_1 = require("../../commandUtil/util");
const cliUtil_1 = require("@mablhq-internal/runtime/core/cliUtil");
const branches_1 = require("@mablhq-internal/runtime/util/branches/branches");
const env_1 = require("@mablhq-internal/runtime/env/env");
const pluralize_1 = __importDefault(require("pluralize"));
const testsUtil_1 = require("@mablhq-internal/runtime/core/execution/testsUtil");
const loggingProvider_1 = require("@mablhq-internal/runtime/providers/logging/loggingProvider");
const runUtil_1 = require("@mablhq-internal/runtime/util/runUtil");
const inquirer = require('inquirer');
const chalk = require('chalk');
const CommandArgTestId = constants_1.CommandArgId;
const TestLaunchCountLimit = 100;
exports.command = 'run-cloud';
exports.describe = 'Run test(s) in the mabl cloud';
exports.builder = (yargs) => {
    yargs
        .example('$0 tests run-cloud --id <id>', 'run test by id')
        .example('$0 tests run-cloud --labels <label1> <label2>', 'run tests by test label')
        .option(CommandArgTestId, {
        describe: 'The id of the test to run',
        type: 'string',
        conflicts: [constants_1.CommandArgLabelsInclude, constants_1.CommandArgLabelsExclude],
    })
        .option(constants_2.CommandArgDeploymentId, {
        alias: constants_2.CommandArgAliases.DeploymentId,
        describe: 'Deployment to run the mabl tests against',
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgUrl, {
        alias: constants_2.CommandArgAliases.Url,
        describe: `Alias for ${constants_2.CommandArgUrlApp}`,
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgUrlApp, {
        describe: 'Application/web URL to run the mabl test against',
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgUrlApi, {
        describe: 'API URL to run the mabl test against',
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgBasicAuthCredentials, {
        alias: constants_2.CommandArgAliases.BasicAuthCredentials,
        describe: 'Basic Authentication credentials to run the test with',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgBrowsers, {
        alias: constants_2.CommandArgAliases.Browsers,
        describe: 'Space delimited browsers to test against (e.g. "chrome edge firefox")',
        type: 'array',
        default: browserTypes_1.DefaultBrowserType,
        choices: constants_2.BrowserTypeSelections,
        demand: 'Choose at least one browser to run on',
    })
        .option(constants_2.CommandArgApiKey, {
        alias: constants_2.CommandArgAliases.ApiKey,
        describe: 'API key (found in the mabl web app)',
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgMablBranch, {
        describe: 'Mabl branch to run test against',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgMablBranchChangesOnly, {
        describe: 'Only execute tests changed on specified branch',
        type: 'boolean',
        default: false,
    })
        .implies(constants_2.CommandArgMablBranchChangesOnly, constants_1.CommandArgMablBranch)
        .option(constants_2.CommandArgRevision, {
        alias: constants_2.CommandArgAliases.Revision,
        describe: 'Code revision hash (application under test)',
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgLabelsInclude, {
        describe: 'Space delimited test labels. Run tests that match any label.',
        type: 'array',
        conflicts: [CommandArgTestId],
    })
        .option(constants_1.CommandArgLabelsExclude, {
        describe: 'Space delimited test labels. Exclude tests that match any label.',
        type: 'array',
        conflicts: [CommandArgTestId],
    })
        .option(constants_1.CommandArgWorkspaceId, {
        alias: constants_2.CommandArgAliases.WorkspaceId,
        describe: 'Workspace to run against',
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgApplicationId, {
        alias: constants_2.CommandArgAliases.ApplicationId,
        describe: `Mabl application to run under. Specify to ensure the test runs with the right URLs. Consider also using the ${constants_1.CommandArgEnvironmentId} flag.`,
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgEnvironmentId, {
        alias: constants_2.CommandArgAliases.EnvironmentId,
        describe: `Mabl environment to run under. Specify to ensure the test runs with environment variables and the latest find information. Consider also using the ${constants_1.CommandArgApplicationId} flag.`,
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgCredentials, {
        alias: constants_2.CommandArgAliases.Credentials,
        describe: 'Credentials ID to run the test with',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgLocale, {
        describe: 'Locale to run the test in, e.g. en-US',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgTimezoneID, {
        describe: 'Identifier of the timezone to run the test in, e.g. America/Buenos_Aires',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgPrompt, {
        describe: 'Prompt to confirm execution selections',
        type: 'boolean',
        default: true,
    })
        .option(constants_2.CommandArgNoPrompt, {
        describe: 'Do not prompt to confirm execution selections',
        alias: constants_2.CommandArgAliases.NoPrompt,
    })
        .check((argv) => (0, testsUtil_1.validateRunCommandWithLabels)(argv[constants_1.CommandArgId], argv[constants_1.CommandArgLabelsInclude], argv[constants_1.CommandArgLabelsExclude]));
};
exports.handler = (0, cliUtil_1.failWrapper)(runInCloud);
async function runInCloud(parsed) {
    const apiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClientFromOptionalApiKey(parsed['api-key']);
    const workspaceId = await (0, util_1.getWorkspaceId)(parsed);
    const prompt = parsed.prompt && !parsed[constants_2.CommandArgNoPrompt];
    const branchName = parsed['mabl-branch'] || constants_1.DefaultBranchName;
    const branchChangesOnly = parsed['branch-changes-only'];
    const maybeTestId = parsed.id;
    const labelsInclude = parsed.labels || [];
    const labelsExclude = parsed['exclude-labels'] || [];
    const maybeDeploymentId = parsed['deployment-id'];
    const maybeUrlApp = parsed['app-url'] ?? parsed.url;
    const maybeUrlApi = parsed['api-url'];
    const browsers = parsed.browsers;
    const credentialsId = parsed['credentials-id'];
    const applicationId = parsed[constants_1.CommandArgApplicationId];
    const environmentId = parsed[constants_1.CommandArgEnvironmentId];
    const basicAuthCredentialsId = parsed[constants_1.CommandArgBasicAuthCredentials];
    const parsedLocale = parsed[constants_2.CommandArgLocale];
    const parsedTimezone = parsed[constants_2.CommandArgTimezoneID];
    const localizationOptions = parsedLocale || parsedTimezone
        ? {
            locale: parsedLocale,
            timezone_identifier: parsedTimezone,
        }
        : undefined;
    let tests = [];
    if (maybeTestId) {
        const journey = await apiClient.getJourney(maybeTestId, branchName);
        tests.push(journey);
    }
    else if (labelsInclude.length > 0 || labelsExclude.length > 0) {
        loggingProvider_1.logger.info('Determining test(s) to run...');
        tests = await getJourneysForLabels(workspaceId, branchName, branchChangesOnly, labelsInclude, labelsExclude, TestLaunchCountLimit, prompt, apiClient);
    }
    else {
        throw new Error(`Inputs error: neither test id or labels provided.`);
    }
    const testCount = tests.length;
    if (testCount === 0) {
        loggingProvider_1.logger.info(chalk.yellow('No tests selected. Quitting.'));
        return 'no-tests-to-run';
    }
    loggingProvider_1.logger.info('\n');
    (0, branches_1.printBranchRunningInfo)(branchName);
    loggingProvider_1.logger.info(`Executing ${(0, pluralize_1.default)('test', testCount, true)} in the mabl cloud...`);
    loggingProvider_1.logger.info(chalk.cyan(`Workspace: `, chalk.magenta(workspaceId)));
    if (applicationId) {
        loggingProvider_1.logger.info(chalk.cyan(`Application: `, chalk.magenta(applicationId)));
    }
    if (environmentId) {
        loggingProvider_1.logger.info(chalk.cyan(`Environment: `, chalk.magenta(environmentId)));
    }
    loggingProvider_1.logger.info(chalk.white('---'));
    const resultUrls = [];
    for (const test of tests) {
        const testRuns = await executeRunCloudTest(test, browsers, branchName, apiClient, maybeDeploymentId, maybeUrlApp, maybeUrlApi, credentialsId, applicationId, environmentId, basicAuthCredentialsId, localizationOptions);
        testRuns.forEach((testRun) => resultUrls.push(`${env_1.BASE_APP_URL}/workspaces/${testRun.organization_id}/test/journey-runs/${testRun.id}`));
        loggingProvider_1.logger.info(chalk.white('---'));
    }
    let outputUrl;
    if (resultUrls.length === 1) {
        outputUrl = resultUrls[0];
        loggingProvider_1.logger.info(`See mabl test output page for results [${chalk.magenta(outputUrl)}]`);
    }
    else {
        let filter = '';
        if (labelsInclude.length === 1) {
            filter = `?labels=${labelsInclude[0]}`;
        }
        outputUrl = `${env_1.BASE_APP_URL}/workspaces/${workspaceId}/output${filter}`;
        loggingProvider_1.logger.info(`See mabl results page to view tests [${chalk.magenta(outputUrl)}]`);
    }
    return outputUrl;
}
async function executeRunCloudTest(test, browsers, branchName, apiClient, maybeDeploymentId, appUrl, apiUrl, credentialsId, applicationId, environmentId, basicAuthCredentialsId, localizationOptions) {
    const effectiveAppUrl = appUrl ??
        (test.test_type === undefined || test.test_type === mablApi_1.TestTypeEnum.Browser
            ? test?.url
            : undefined);
    const effectiveApiUrl = apiUrl ??
        (test.test_type && test.test_type !== mablApi_1.TestTypeEnum.Browser
            ? test?.api_url
            : undefined);
    const workspaceId = test.organization_id;
    const testId = test.invariant_id;
    let deploymentIds;
    if (!effectiveAppUrl && !effectiveApiUrl && !maybeDeploymentId) {
        const urlCmdLineArg = test.test_type === mablApi_1.TestTypeEnum.Browser
            ? `--${constants_1.CommandArgUrl}, --${constants_2.CommandArgUrlApp}`
            : `--${constants_1.CommandArgUrlApi}`;
        throw new Error(`There is no default URL for test [${testId}]. Please specify ${urlCmdLineArg} or --${constants_2.CommandArgDeploymentId}`);
    }
    loggingProvider_1.logger.info(chalk.cyan(`Running test: `, chalk.magenta(`${test.name} - ${testId} - on branch - ${branchName}`)));
    if (effectiveAppUrl) {
        loggingProvider_1.logger.info(chalk.cyan(`App/web URL: `, chalk.magenta(effectiveAppUrl)));
    }
    if (effectiveApiUrl) {
        loggingProvider_1.logger.info(chalk.cyan(`API URL: `, chalk.magenta(effectiveApiUrl)));
    }
    if (applicationId || environmentId) {
        const deploymentsResult = await apiClient.queryDeploymentEntities(workspaceId, environmentId, applicationId);
        const testType = test.test_type ?? mablApi_1.TestTypeEnum.Browser;
        const expectedDeploymentType = (0, runUtil_1.getExpectedDeploymentType)(testType);
        const foundDeployments = deploymentsResult.deployments
            ? deploymentsResult.deployments.filter((deployment) => (!applicationId || deployment.application_id === applicationId) &&
                (!environmentId || deployment.environment_id === environmentId) &&
                (0, runUtil_1.isDeploymentMatchByType)(deployment, expectedDeploymentType))
            : [];
        if (!foundDeployments.length) {
            throw new Error('No configuration was found for the applicationId/environmentId specified');
        }
        deploymentIds = foundDeployments.map((deployment) => deployment.id);
    }
    const planRun = await apiClient.postPlanRun(workspaceId, testId, branchName, browsers, effectiveAppUrl, effectiveApiUrl, maybeDeploymentId, credentialsId, deploymentIds, basicAuthCredentialsId, localizationOptions);
    const testRunsQueryResult = await apiClient.getTestRunsForPlan(planRun.id);
    return testRunsQueryResult.test_script_executions ?? [];
}
async function getJourneysForLabels(workspaceId, branchName, branchChangesOnly, labelsInclude, labelsExclude, limit, prompt, apiClient) {
    const journeys = await apiClient.getJourneys({
        limit,
        organization_id: workspaceId,
        include_labels: labelsInclude,
        exclude_labels: labelsExclude,
        branch: branchName,
        branch_changes_only: branchChangesOnly,
    });
    switch (journeys.length) {
        case 0:
            throw new Error(`No tests matched the labels include [${labelsInclude.join(',')}] exclude [${labelsExclude.join(',')}]`);
        case TestLaunchCountLimit:
            loggingProvider_1.logger.info(chalk.yellow(`WARNING: Maximum test search limit of ${TestLaunchCountLimit} test matches hit.`));
            loggingProvider_1.logger.info(chalk.yellow(`The ${TestLaunchCountLimit} most recently created tests will be considered.`));
    }
    if (!prompt) {
        return journeys;
    }
    const journeyChoices = journeys.map((journey, index) => ({
        name: `${index})\t${journey.id}\t${journey.name}`,
        value: journey.id,
        checked: true,
    }));
    const testsToRun = await inquirer.prompt([
        {
            type: 'checkbox',
            name: 'test_selection',
            message: `Run these [${journeys.length}] tests?`,
            pageSize: 20,
            choices: journeyChoices,
            default: journeyChoices.map((choice) => choice.value),
        },
    ]);
    const selectedJourneys = journeys.filter((journey) => testsToRun?.test_selection?.includes(journey.id)) ?? [];
    if (selectedJourneys.length === 0) {
        return [];
    }
    const response = await inquirer.prompt([
        {
            type: 'list',
            name: 'do_continue',
            message: `Run [${selectedJourneys.length}] tests?`,
            choices: [
                { name: 'Yes', value: true },
                { name: 'No', value: false },
            ],
            default: 'Yes',
        },
    ]);
    if (response?.do_continue) {
        return selectedJourneys;
    }
    return [];
}
