/* jscpd:ignore-start */
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import c from 'chalk';
import { assert } from 'console';
import fs from 'fs-extra';
import moment from 'moment';
import * as os from 'os';
import * as path from 'path';
import { clearCache } from '../../../common/cache/index.js';
import { elapseEnd, elapseStart, execSfdxJson, getCurrentGitBranch, uxLog } from '../../../common/utils/index.js';
import { initApexScripts, initOrgData, initPermissionSetAssignments, promptUserEmail, } from '../../../common/utils/orgUtils.js';
import { WebSocketClient } from '../../../common/websocketClient.js';
import { getConfig } from '../../../config/index.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('sfdx-hardis', 'org');
export default class SandboxCreate extends SfCommand {
    static title = 'Create sandbox org';
    static description = `
## Command Behavior

**Creates and initializes a Salesforce sandbox org.**

This command automates the process of provisioning a new sandbox environment, making it ready for development or testing. It handles various aspects of sandbox creation and initial setup, reducing manual effort and ensuring consistency.

Key functionalities:

- **Sandbox Definition:** Uses a \`project-sandbox-def.json\` file (if present in \`config/\`) to define sandbox properties like name, description, license type, and source sandbox. If not provided, it uses default values.
- **Dynamic Naming:** Generates a unique sandbox alias based on the current username, Git branch, and a timestamp.
- **Sandbox Creation:** Executes the Salesforce CLI command to create the sandbox, including setting it as the default org and waiting for its completion.
- **User Update:** Updates the main sandbox user's details (e.g., Last Name, First Name) and can fix country values or marketing user permissions if needed.
- **Initialization Scripts:** Runs predefined Apex scripts, assigns permission sets, and imports initial data into the newly created sandbox, based on configurations in your project.
- **Error Handling:** Provides detailed error messages for common sandbox creation issues, including Salesforce-specific errors.

<details markdown="1">
<summary>Technical explanations</summary>

The command's technical implementation involves:

- **Configuration Loading:** It loads project and user configurations using \`getConfig\` to retrieve settings like \`projectName\`, \`devHubAlias\`, and \`userEmail\`.
- **Git Integration:** Retrieves the current Git branch name using \`getCurrentGitBranch\` to inform sandbox naming.
- **File System Operations:** Uses \`fs-extra\` to manage sandbox definition files (reading \`project-sandbox-def.json\`, writing a user-specific definition file) and temporary directories.
- **Salesforce CLI Execution:** Executes Salesforce CLI commands (\`sf org create sandbox\`, \`sf data get record\`, \`sf data update record\`, \`sf org open\`) using \`execSfdxJson\` for sandbox creation, user updates, and opening the org in a browser.
- **Cache Management:** Clears the Salesforce CLI org list cache (\`clearCache('sf org list')\`) to ensure the newly created sandbox is immediately recognized.
- **Initialization Utilities:** Calls a suite of utility functions (\`initPermissionSetAssignments\`, \`initApexScripts\`, \`initOrgData\`) to perform post-creation setup tasks.
- **Error Assertions:** Uses \`assert\` to check the success of Salesforce CLI commands and provides custom error messages for better debugging.
- **WebSocket Communication:** Uses \`WebSocketClient.sendRefreshStatusMessage\` to notify connected VS Code clients about the new sandbox.
- **Required Plugin Check:** Explicitly lists \`sfdmu\` as a required plugin, indicating its role in data initialization.
</details>
`;
    static examples = ['$ sf hardis:org:create'];
    // public static args = [{name: 'file'}];
    static flags = {
        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',
        }),
    };
    static supportsDevhubUsername = true;
    // Set this to true if your command requires a project workspace; 'requiresProject' is false by default
    static requiresProject = true;
    // List required plugins, their presence will be tested before running the command
    static requiresSfdxPlugins = ['sfdmu'];
    /* jscpd:ignore-end */
    debugMode = false;
    configInfo;
    devHubAlias;
    sandboxOrgAlias;
    userEmail;
    gitBranch;
    projectSandboxDef;
    sandboxOrgInfo;
    sandboxOrgUsername;
    sandboxOrgSfdxAuthUrl;
    authFileJson;
    projectName;
    sandboxOrgFromPool;
    async run() {
        const { flags } = await this.parse(SandboxCreate);
        this.debugMode = flags.debug || false;
        elapseStart(`Create and initialize sandbox org`);
        await this.initConfig();
        await this.createSandboxOrg();
        try {
            await this.updateSandboxOrgUser();
            await initPermissionSetAssignments(this.configInfo.initPermissionSets || [], this.sandboxOrgUsername);
            await initApexScripts(this.configInfo.sandboxOrgInitApexScripts || [], this.sandboxOrgUsername);
            await initOrgData(path.join('.', 'scripts', 'data', 'SandboxInit'), this.sandboxOrgUsername);
        }
        catch (e) {
            elapseEnd(`Create and initialize sandbox org`);
            uxLog("log", this, c.grey('Error: ' + e.message + '\n' + e.stack));
            throw e;
        }
        elapseEnd(`Create and initialize sandbox org`);
        // Return an object to be displayed with --json
        return {
            status: 0,
            sandboxOrgAlias: this.sandboxOrgAlias,
            sandboxOrgInfo: this.sandboxOrgInfo,
            sandboxOrgUsername: this.sandboxOrgUsername,
            sandboxOrgSfdxAuthUrl: this.sandboxOrgSfdxAuthUrl,
            authFileJson: this.authFileJson,
            outputString: 'Created and initialized sandbox org',
        };
    }
    // Initialize configuration from .sfdx-hardis.yml + .gitbranch.sfdx-hardis.yml + .username.sfdx-hardis.yml
    async initConfig() {
        this.configInfo = await getConfig('user');
        this.gitBranch = (await getCurrentGitBranch({ formatted: true })) || '';
        const newSandboxName = os.userInfo().username +
            '-' +
            (this.gitBranch.split('/').pop() || '').slice(0, 15) +
            '_' +
            moment().format('YYYYMMDD_hhmm');
        this.sandboxOrgAlias = process.env.SANDBOX_ORG_ALIAS || newSandboxName;
        this.projectName = process.env.PROJECT_NAME || this.configInfo.projectName;
        this.devHubAlias = process.env.DEVHUB_ALIAS || this.configInfo.devHubAlias;
        this.userEmail = process.env.USER_EMAIL || process.env.GITLAB_USER_EMAIL || this.configInfo.userEmail;
        // If not found, prompt user email and store it in user config file
        if (this.userEmail == null) {
            this.userEmail = await promptUserEmail();
        }
    }
    // Create a new sandbox org or reuse existing one
    async createSandboxOrg() {
        // Build project-sandbox-def-branch-user.json
        uxLog("action", this, c.cyan('Building custom project-sandbox-def.json...'));
        if (fs.existsSync('./config/project-sandbox-def.json')) {
            this.projectSandboxDef = JSON.parse(fs.readFileSync('./config/project-sandbox-def.json', 'utf-8'));
        }
        else {
            uxLog("warning", this, c.yellow(`Default values used: you may define a file ${c.bold('config/project-sandbox-def.json')}`));
            this.projectSandboxDef = {
                sandboxName: '',
                description: 'SFDX Hardis developer sandbox',
                licenseType: 'Developer',
                sourceSandbox: '',
            };
        }
        this.projectSandboxDef.sandboxName = os.userInfo().username.substring(0, 10);
        const projectSandboxDefLocal = `./config/user/project-sandbox-def-${this.sandboxOrgAlias}.json`;
        await fs.ensureDir(path.dirname(projectSandboxDefLocal));
        await fs.writeFile(projectSandboxDefLocal, JSON.stringify(this.projectSandboxDef, null, 2));
        // Fix @salesforce/cli bug: remove shape.zip if found
        const tmpShapeFolder = path.join(os.tmpdir(), 'shape');
        if (fs.existsSync(tmpShapeFolder)) {
            await fs.remove(tmpShapeFolder);
            uxLog("log", this, c.grey('Deleted ' + tmpShapeFolder));
        }
        // Create new sandbox org
        uxLog("action", this, c.cyan('Creating new sandbox org...'));
        const waitTime = process.env.SANDBOX_ORG_WAIT || '60';
        const createCommand = 'sf org create sandbox --set-default ' +
            `--definition-file ${projectSandboxDefLocal} ` +
            `--alias ${this.sandboxOrgAlias} ` +
            `--wait ${waitTime} ` +
            `--target-org ${this.devHubAlias} `;
        const createResult = await execSfdxJson(createCommand, this, {
            fail: false,
            output: false,
            debug: this.debugMode,
        });
        await clearCache('sf org list');
        assert(createResult.status === 0 && createResult.result, this.buildSandboxCreateErrorMessage(createResult));
        this.sandboxOrgInfo = createResult.result;
        this.sandboxOrgUsername = this.sandboxOrgInfo.username;
        // Trigger a status refresh on VsCode WebSocket Client
        WebSocketClient.sendRefreshStatusMessage();
        // Open sandbox org for user if not in CI
        await execSfdxJson('sf org open', this, {
            fail: true,
            output: false,
            debug: this.debugMode,
        });
        uxLog("action", this, c.cyan(`Created sandbox org ${c.green(this.sandboxOrgAlias)} with user ${c.green(this.sandboxOrgUsername)}`));
    }
    buildSandboxCreateErrorMessage(createResult) {
        if (createResult.status === 0 && createResult.result) {
            return c.green('Sandbox create OK');
        }
        else if (createResult.status === 1 &&
            createResult.errorMessage.includes('Socket timeout occurred while listening for results')) {
            return c.red(`[sfdx-hardis] Error creating sandbox org. ${c.bold('This is probably a Salesforce error, try again manually or launch again CI job')}\n${JSON.stringify(createResult, null, 2)}`);
        }
        return c.red(`[sfdx-hardis] Error creating sandbox org. Maybe try ${c.yellow(c.bold('sf hardis:sandbox:create --forcenew'))} ?\n${JSON.stringify(createResult, null, 2)}`);
    }
    // Update sandbox org user
    async updateSandboxOrgUser() {
        const config = await getConfig('user');
        // Update sandbox org main user
        uxLog("action", this, c.cyan('Update / fix sandbox org user ' + this.sandboxOrgUsername));
        const userQueryCommand = `sf data get record --sobject User --where "Username=${this.sandboxOrgUsername}" --target-org ${this.sandboxOrgAlias}`;
        const userQueryRes = await execSfdxJson(userQueryCommand, this, {
            fail: true,
            output: false,
            debug: this.debugMode,
        });
        let updatedUserValues = `LastName='SFDX-HARDIS' FirstName='Sandbox Org'`;
        // Fix country value is State & Country picklist activated
        /* jscpd:ignore-start */
        if ((this.projectSandboxDef.features || []).includes('StateAndCountryPicklist') &&
            userQueryRes.result.CountryCode == null) {
            updatedUserValues += ` CountryCode='${config.defaultCountryCode || 'FR'}' Country='${config.defaultCountry || 'France'}'`;
        }
        if ((this.projectSandboxDef.features || []).includes('MarketingUser') &&
            userQueryRes.result.UserPermissionsMarketingUser === false) {
            // Make sure MarketingUser is checked on sandbox org user if it is supposed to be
            updatedUserValues += ' UserPermissionsMarketingUser=true';
        }
        const userUpdateCommand = `sf data update record --sobject User --record-id ${userQueryRes.result.Id} --values "${updatedUserValues}" --target-org ${this.sandboxOrgAlias}`;
        await execSfdxJson(userUpdateCommand, this, { fail: false, output: true, debug: this.debugMode });
        /* jscpd:ignore-end */
    }
}
//# sourceMappingURL=create.js.map