import { SfCommand, Flags, requiredOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import c from 'chalk';
import fs from 'fs-extra';
import * as path from 'path';
import { execCommand, extractRegexMatchesMultipleGroups, uxLog } from '../../../../common/utils/index.js';
import { getNotificationButtons, getOrgMarkdown } from '../../../../common/utils/notifUtils.js';
import { CONSTANTS, getConfig, getReportDirectory } from '../../../../config/index.js';
import { NotifProvider } from '../../../../common/notifProvider/index.js';
import { generateApexCoverageOutputFile } from '../../../../common/utils/deployUtils.js';
import { setConnectionVariables } from '../../../../common/utils/orgUtils.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('sfdx-hardis', 'org');
export default class OrgTestApex extends SfCommand {
    static title = 'Run apex tests';
    static description = `Run apex tests in Salesforce org

If following configuration is defined, it will fail if apex coverage target is not reached:

- Env \`APEX_TESTS_MIN_COVERAGE_ORG_WIDE\` or \`.sfdx-hardis\` property \`apexTestsMinCoverageOrgWide\`
- Env \`APEX_TESTS_MIN_COVERAGE_ORG_WIDE\` or \`.sfdx-hardis\` property \`apexTestsMinCoverageOrgWide\`

You can override env var SFDX_TEST_WAIT_MINUTES to wait more than 60 minutes.

This command is part of [sfdx-hardis Monitoring](${CONSTANTS.DOC_URL_ROOT}/salesforce-monitoring-apex-tests/) and can output Grafana, Slack and MsTeams Notifications.
`;
    static examples = ['$ sf hardis:org:test:apex'];
    static flags = {
        testlevel: Flags.string({
            char: 'l',
            default: 'RunLocalTests',
            options: ['NoTestRun', 'RunSpecifiedTests', 'RunLocalTests', 'RunAllTestsInOrg'],
            description: messages.getMessage('testLevel'),
        }),
        debug: Flags.boolean({
            char: 'd',
            default: false,
            description: messages.getMessage('debugMode'),
        }),
        websocket: Flags.string({
            description: messages.getMessage('websocket'),
        }),
        skipauth: Flags.boolean({
            description: 'Skip authentication check when a default username is required',
        }),
        'target-org': requiredOrgFlagWithDeprecations,
    };
    // Set this to true if your command requires a project workspace; 'requiresProject' is false by default
    // protected static requiresProject = true;
    configInfo = {};
    testRunOutcome;
    testRunOutputString;
    statusMessage;
    coverageTarget = 75.0;
    coverageValue = 0.0;
    failingTestClasses = [];
    notifSeverity = 'log';
    notifText;
    notifAttachments = [];
    notifAttachedFiles = [];
    orgMarkdown = '';
    notifButtons = [];
    /* jscpd:ignore-start */
    async run() {
        const { flags } = await this.parse(OrgTestApex);
        const testlevel = flags.testlevel || 'RunLocalTests';
        const debugMode = flags.debug || false;
        this.configInfo = await getConfig('branch');
        const orgInstanceUrl = flags['target-org']?.getConnection()?.instanceUrl || '';
        this.orgMarkdown = await getOrgMarkdown(orgInstanceUrl);
        this.notifButtons = await getNotificationButtons();
        /* jscpd:ignore-end */
        uxLog("action", this, c.cyan(`Running Apex tests in org ${orgInstanceUrl} with test level: ${testlevel}`));
        await this.runApexTests(testlevel, debugMode, flags['target-org']?.getUsername());
        // No Apex
        if (this.testRunOutcome === 'NoApex') {
            this.notifSeverity = 'log';
            this.statusMessage = 'No Apex found in the org';
            this.notifText = `No Apex found in org ${this.orgMarkdown}`;
            uxLog("log", this, c.grey(this.statusMessage));
        }
        // Failed tests
        else if (this.testRunOutcome === 'Failed') {
            await this.processApexTestsFailure();
        }
        else if (this.testRunOutcome === 'Passed') {
            uxLog("success", this, c.green(`Apex tests passed (${this.testRunOutcome})`));
        }
        // Get test coverage (and fail if not reached)
        await this.checkOrgWideCoverage();
        await this.checkTestRunCoverage();
        // uxLog("log", this, c.grey(this.statusMessage));
        uxLog("other", this, `Apex coverage: ${this.coverageValue}% (target: ${this.coverageTarget}%)`);
        await setConnectionVariables(flags['target-org']?.getConnection()); // Required for some notifications providers like Email
        await NotifProvider.postNotifications({
            type: 'APEX_TESTS',
            text: this.notifText,
            attachments: this.notifAttachments,
            buttons: this.notifButtons,
            severity: this.notifSeverity,
            attachedFiles: this.notifAttachedFiles,
            logElements: this.failingTestClasses,
            data: {
                metric: this.failingTestClasses.length,
                coverageTarget: this.coverageTarget,
                coverageValue: this.coverageValue,
            },
            metrics: {
                ApexTestsFailingClasses: this.failingTestClasses.length,
                ApexTestsCodeCoverage: this.coverageValue,
            },
        });
        // Handle output message & exit code
        if (this.notifSeverity === 'error') {
            process.exitCode = 1;
            uxLog("error", this, c.red(this.statusMessage));
        }
        else {
            uxLog("success", this, c.green(this.statusMessage));
        }
        return { orgId: flags['target-org'].getOrgId(), outputString: this.statusMessage, statusCode: process.exitCode };
    }
    async runApexTests(testlevel, debugMode, orgUsername) {
        // Run tests with SFDX commands
        const reportDir = await getReportDirectory();
        const testCommand = 'sf apex run test' +
            ' --code-coverage' +
            ' --result-format human' +
            ` --output-dir ${reportDir}` +
            ` --wait ${process.env.SFDX_TEST_WAIT_MINUTES || '60'}` +
            ` --test-level ${testlevel}` +
            (orgUsername ? ` --target-org ${orgUsername}` : '') +
            (debugMode ? ' --verbose' : '');
        try {
            const execCommandRes = await execCommand(testCommand, this, {
                output: true,
                debug: debugMode,
                fail: true,
            });
            // Parse outcome value from logs with Regex
            this.testRunOutcome = (/Outcome *(.*) */.exec(execCommandRes.stdout + execCommandRes.stderr) || '')[1].trim();
            this.testRunOutputString = execCommandRes.stdout + execCommandRes.stderr;
            await generateApexCoverageOutputFile();
        }
        catch (e) {
            // No Apex in the org
            if (e.message.includes('Toujours fournir une propriété classes, suites, tests ou testLevel') ||
                e.message.includes('Always provide a classes, suites, tests, or testLevel property')) {
                this.testRunOutcome = 'NoApex';
            }
            else {
                // Failing Apex tests
                this.testRunOutputString = e.message;
                this.testRunOutcome = 'Failed';
                await generateApexCoverageOutputFile();
            }
        }
    }
    async processApexTestsFailure() {
        this.notifSeverity = 'error';
        const reportDir = await getReportDirectory();
        // Parse log from external file
        const sfReportFile = path.join(reportDir, '/test-result.txt');
        if (fs.existsSync(sfReportFile)) {
            this.notifAttachedFiles = [sfReportFile];
        }
        // Parse failing test classes
        const failuresRegex = /(.*) Fail (.*)/gm;
        const regexMatches = await extractRegexMatchesMultipleGroups(failuresRegex, this.testRunOutputString);
        for (const match of regexMatches) {
            this.failingTestClasses.push({ name: match[1].trim(), error: match[2].trim() });
        }
        this.notifAttachments = [
            {
                text: this.failingTestClasses
                    .map((failingTestClass) => {
                    return '• *' + failingTestClass.name + '*: ' + failingTestClass.error;
                })
                    .join('\n'),
            },
        ];
        this.statusMessage = `Apex tests failed (${this.failingTestClasses.length}). (Outcome: ${this.testRunOutcome})`;
        this.notifText = `Apex tests failed (${this.failingTestClasses.length}) in org ${this.orgMarkdown} (Outcome: ${this.testRunOutcome})`;
        const failedTestsString = this.failingTestClasses
            .map((failingTestClass) => {
            return `- ${failingTestClass.name}: ${failingTestClass.error}`;
        })
            .join('\n');
        uxLog("warning", this, c.yellow("Failing Apex tests:\n" + failedTestsString));
    }
    async checkOrgWideCoverage() {
        const coverageOrgWide = parseFloat((/Org Wide Coverage *(.*)/.exec(this.testRunOutputString) || '')[1].replace('%', ''));
        const minCoverageOrgWide = parseFloat(process.env.APEX_TESTS_MIN_COVERAGE_ORG_WIDE ||
            process.env.APEX_TESTS_MIN_COVERAGE ||
            this.configInfo.apexTestsMinCoverageOrgWide ||
            this.configInfo.apexTestsMinCoverage ||
            75.0);
        this.coverageTarget = minCoverageOrgWide;
        this.coverageValue = coverageOrgWide;
        // Do not test if tests failed
        if (this.testRunOutcome !== 'Passed') {
            return;
        }
        // Developer tried to cheat in config ^^
        if (minCoverageOrgWide < 75.0) {
            this.notifSeverity = 'error';
            this.statusMessage = `Don't try to cheat with configuration: Minimum org wide coverage must be 75% ;)`;
            this.notifText = this.statusMessage;
        }
        // Min coverage not reached
        else if (coverageOrgWide < minCoverageOrgWide) {
            this.notifSeverity = 'error';
            this.statusMessage = `Test run coverage (org wide) *${coverageOrgWide}%* should be > to ${minCoverageOrgWide}%`;
            this.notifText = `${this.statusMessage} in ${this.orgMarkdown}`;
        }
        // We are good !
        else {
            this.notifSeverity = 'log';
            this.statusMessage = `Test run coverage (org wide) *${coverageOrgWide}%* is > to ${minCoverageOrgWide}%`;
            this.notifText = `${this.statusMessage} in ${this.orgMarkdown}`;
        }
    }
    async checkTestRunCoverage() {
        if (this.testRunOutputString.includes('Test Run Coverage')) {
            // const coverageTestRun = parseFloat(testRes.result.summary.testRunCoverage.replace('%', ''));
            const coverageTestRun = parseFloat((/Test Run Coverage *(.*)/.exec(this.testRunOutputString) || '')[1].replace('%', ''));
            const minCoverageTestRun = parseFloat(process.env.APEX_TESTS_MIN_COVERAGE_TEST_RUN ||
                process.env.APEX_TESTS_MIN_COVERAGE ||
                this.configInfo.apexTestsMinCoverage ||
                this.coverageTarget);
            this.coverageTarget = minCoverageTestRun;
            this.coverageValue = coverageTestRun;
            // Do not test if tests failed
            if (this.testRunOutcome !== 'Passed') {
                return;
            }
            // Developer tried to cheat in config ^^
            if (minCoverageTestRun < 75.0) {
                this.notifSeverity = 'error';
                this.statusMessage = `Don't try to cheat with configuration: Minimum test run coverage must be 75% ;)`;
                this.notifText = this.statusMessage;
            }
            // Min coverage not reached
            else if (coverageTestRun < minCoverageTestRun) {
                this.notifSeverity = 'error';
                this.statusMessage = `Test run coverage *${coverageTestRun}%* should be > to ${minCoverageTestRun}%`;
                this.notifText = `${this.statusMessage} in ${this.orgMarkdown}`;
            }
            // We are good !
            else {
                this.notifSeverity = 'log';
                this.statusMessage = `Test run coverage *${coverageTestRun}%* is > to ${minCoverageTestRun}%`;
                this.notifText = `${this.statusMessage} in ${this.orgMarkdown}`;
            }
        }
    }
}
//# sourceMappingURL=apex.js.map