import { Option } from 'commander';
import { PrettyPrint } from '../output/pretty_print.js';

export class Applications {
    constructor(program, logger, services) {
        this.logger = logger;
        this.services = services;
        services["applications"] = this;

        logger.debug('Applications.constructor');

        // applications - get all applications
        program
            .command('applications')
            .description('Get applications')
            .addOption(new Option('-p, --page <number>', 'display specific page'))
            .addOption(new Option('-l, --limit <number>', 'limit number of results per page (max: 1000)').conflicts('max'))
            .addOption(new Option('-m, --max', 'same as --limit 1000').conflicts('limit'))
            .addOption(new Option('-f, --file <filename>', 'save results to file (CSV format)'))
            .addOption(new Option('-j, --json', 'display raw JSON').conflicts('interactive'))
            .addOption(new Option('-n, --notdeployed', 'filter on not deployed applications'))
            .addOption(new Option('-i, --indeployment', 'filter on in deployment applications'))
            .addOption(new Option('-d, --deployed', 'filter on deployed applications'))
            .addOption(new Option('--active', 'filter on active applications'))
            .addOption(new Option('--blocked', 'filter on blocked applications'))
            .addOption(new Option('--new', 'filter on new applications'))
            .addOption(new Option('--deleted', 'filter on deleted applications'))
            .addOption(new Option('--stopped', 'filter on stopped applications'))
            .addOption(new Option('--notactive', 'filter on not active applications'))
            .addOption(new Option('-s, --withsubscription', 'filter on applications with subscription'))
            .addOption(new Option('--owner <userid>', 'filter on applications owned by user').conflicts('own'))
            .addOption(new Option('--name <name>', 'filter on applications with name'))
            .addOption(new Option('--type <type>', 'filter on applications type'))
            .addOption(new Option('--offer <offer>', 'filter on applications offer'))
            .addOption(new Option('--bydate', 'sort applications by date (more recent first)'))
            .addOption(new Option('--format <format>', 'output format: small, medium or full (default: small)').default('small'))
            .addOption(new Option('--properties <properties...>', 'filter by any supported properties: displayName:Doe isTerminated:true'))
            .addOption(new Option('--columns <columns...>', 'filter columns to be displayed'))
            .addOption(new Option('--own', 'filter on own applications').conflicts('owner'))
            .addOption(new Option('--interactive', 'show result in interactive display').conflicts('json'))
            .action(async (options) => {
                if (options.interactive) {
                    options.limit = (options.limit ? options.limit : 100);
                    const json = await this.getApplications(options);
                    this.services['interactive_display'].displayObjectNavigator(json, 'applications', options);
                }
                else {
                    PrettyPrint.withSpinner(
                        async () => { return await this.getApplications(options); },
                        (json) => { PrettyPrint.display(json, (options.columns ? options.columns : undefined), options.json); }
                    );
                }
            });

        // applications - get application
        const applicationCommand = program
            .command('application')
            .description('Manage applications');
        this.applicationCommand = applicationCommand;

        applicationCommand
            .command('get')
            .argument('<id>', 'id of the application')
            .description('Display application details')
            .addOption(new Option('--columns <columns...>', 'filter columns to be displayed'))
            .addOption(new Option('-j, --json', 'display raw JSON').conflicts('interactive'))
            .addOption(new Option('--interactive', 'show result in interactive display').conflicts('json'))
            .action(async (id, options) => {
                if (options.interactive) {
                    const json = await this.getApplication(id, options);
                    this.services['interactive_display'].displayObjectNavigator(json);
                }
                else {
                    PrettyPrint.withSpinner(
                        async () => { return await this.getApplication(id, options); },
                        (json) => { PrettyPrint.display(json, (options.columns ? options.columns : undefined), options.json); }
                    );
                }
            });

        // applications - create application
        applicationCommand
            .command('create')
            .argument('<name>', 'name of the application')
            .description('Create a new application object')
            .addOption(new Option('--type <type>', 'application type (web, mobile, desktop, server, bot, iot, admin. Defaults to web)').default('web'))
            .addOption(new Option('--offer <offer>', 'application offer (business, payasyougo, appconnect, internal. Defaults to payasyougo)').default('payasyougo'))
            .addOption(new Option('--activity <activity>', 'application activity A..U. Defaults to J').default('J'))
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .addOption(new Option('--owner <ownerid>', 'application owner'))
            .action(async (name, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.createApplication(name, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - delete application
        applicationCommand
            .command('delete')
            .argument('<id>', 'id of the application')
            .addOption(new Option('--force', 'force application deletion (USE WITH CAUTION /!\)'))
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Delete an application object')
            .action(async (id, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.deleteApplication(id, options) },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set application offer
        applicationCommand
            .command('set-offer')
            .argument('<id>', 'id of the application')
            .argument('<offer>', 'application ofer (business, payasyougo, appconnect, internal)')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set offer off an existing application')
            .action(async (id, offer, options) => {
                options['offer'] = offer;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json) }
                );
            });

        // applications - request deployment of application
        applicationCommand
            .command('deploy')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Request deployment of an application')
            .action(async (id, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.actionApplication('request-deploy', id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - approve deployment of application
        applicationCommand
            .command('approve')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Approve deployment of an application')
            .addOption(new Option('-r, --reason <reason>', 'deployment reason'))
            .action(async (id, options) => {
                if (!options.reason) {
                    const { default: input } = await import('@inquirer/input');
                    while (!options.reason) {
                        options.reason = await input({ message: 'Deployment reason?' });
                    }
                }

                PrettyPrint.withSpinner(
                    async () => { return await this.actionApplication('deploy', id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - decline deployment of application
        applicationCommand
            .command('decline')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Decline deployment of an application')
            .action(async (id, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.actionApplication('decline', id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - block application
        applicationCommand
            .command('block')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Block an application')
            .action(async (id, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.actionApplication('block', id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - unblock application
        applicationCommand
            .command('unblock')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Unblock an application')
            .action(async (id, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.actionApplication('unblock', id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        applicationCommand
            .command('renew')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .addOption(new Option('--expirationdate <date>', 'new expiration date in format YYYY-MM-DD'))
            .description('Renew demo application period (only for demo applications)')
            .action(async (id, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.actionApplication('renew', id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // Command to stop an application
        applicationCommand
            .command('stop')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Stop an application')
            .action(async (id, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.actionApplication('stop', id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // Command to restart an application
        applicationCommand
            .command('restart')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Restart an application')
            .action(async (id, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.actionApplication('restart', id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        applicationCommand
            .command('allow-sending-sms')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Allow the application to send SMS')
            .action(async (id, options) => {
                options['allowSendingSms'] = true;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - disallowSendingSMS
        applicationCommand
            .command('disallow-sending-sms')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Do not allow the application to send SMS')
            .action(async (id, options) => {
                options['allowSendingSms'] = false;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set a comment on an application (only for users with role app_superadmin or superadmin)
        applicationCommand
            .command('set-comment')
            .argument('<id>', 'id of the application')
            .argument('<comment>', 'comment to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set a comment on an application (only for users with role app_superadmin or superadmin)')
            .action(async (id, comment, options) => {
                options['appSuperadminComment'] = comment;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set a status on an application (only for users with role app_superadmin or superadmin)
        applicationCommand
            .command('set-status')
            .argument('<id>', 'id of the application')
            .argument('<status>', 'status to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set a status on an application (only for users with role app_superadmin or superadmin)')
            .action(async (id, status, options) => {
                options['appSuperadminStatus'] = status;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set an array of Oauth redirect URIs on an application
        applicationCommand
            .command('set-oauth-redirect-uris')
            .argument('<id>', 'id of the application')
            .argument('<uris...>', 'list of Oauth redirect URIs to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set an array of Oauth redirect URIs on an application')
            .action(async (id, uris, options) => {
                options['oauthRedirectUris'] = uris;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set terms of service URL on an application
        applicationCommand
            .command('set-terms-of-service-url')
            .argument('<id>', 'id of the application')
            .argument('<url>', 'URL of the terms of service to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set terms of service URL on an application')
            .action(async (id, url, options) => {
                options['termsOfServiceUrl'] = url;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set privacy policy URL on an application
        applicationCommand
            .command('set-privacy-policies-url')
            .argument('<id>', 'id of the application')
            .argument('<url>', 'URL of the privacy policies to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set privacy policies URL on an application')
            .action(async (id, url, options) => {
                options['privacyPoliciesUrl'] = url;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set activity on an application
        applicationCommand
            .command('set-activity')
            .argument('<id>', 'id of the application')
            .argument('<activity>', 'activity to set on the application (A..U)')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set activity on an application')
            .action(async (id, activity, options) => {
                options['activity'] = activity;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set backchannel authentication redirect URL on an application
        applicationCommand
            .command('set-backchannel-authentication-redirect-uri')
            .argument('<id>', 'id of the application')
            .argument('<url>', 'URL of the backchannel authentication redirect to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set backchannel authentication redirect URL on an application')
            .action(async (id, url, options) => {
                options['backchannelAuthenticationRedirectUri'] = url;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set deploy reason on an application (only for users with role app_superadmin or superadmin)
        applicationCommand
            .command('set-deploy-reason')
            .argument('<id>', 'id of the application')
            .argument('<reason>', 'reason to set on the application for deployment (only for users with role app_superadmin or superadmin)')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set deploy reason on an application (only for users with role app_superadmin or superadmin)')
            .action(async (id, reason, options) => {
                options['deployReason'] = reason;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set description on an application
        applicationCommand
            .command('set-description')
            .argument('<id>', 'id of the application')
            .argument('<description>', 'description to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set description on an application')
            .action(async (id, description, options) => {
                options['description'] = description;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - disable OAuth consent screen for an application (only for users with role app_superadmin or superadmin)
        applicationCommand
            .command('disable-oauth-consent')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Disable OAuth consent screen for an application (only for users with role app_superadmin or superadmin)')
            .action(async (id, options) => {
                options['disableOAuthConsentScreen'] = true;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - enable OAuth consent screen for an application (only for users with role app_superadmin or superadmin)
        applicationCommand
            .command('enable-oauth-consent')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Enable OAuth consent screen for an application (only for users with role app_superadmin or superadmin)')
            .action(async (id, options) => {
                options['disableOAuthConsentScreen'] = false;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });
        
        // applications - enable OAuth allow several refresh tokens for an application
        applicationCommand
            .command('allow-several-refresh-tokens')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Allow several refresh tokens for an application')
            .action(async (id, options) => {
                options['enableOAuthAllowSeveralRefreshTokens'] = true;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - disable OAuth allow several refresh tokens for an application
        applicationCommand
            .command('disallow-several-refresh-tokens')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Disallow several refresh tokens for an application')
            .action(async (id, options) => {
                options['enableOAuthAllowSeveralRefreshTokens'] = false;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - allow OAuth implicit grant flow for an application
        applicationCommand
            .command('allow-implicit-grant')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Allow OAuth implicit grant flow for an application')
            .action(async (id, options) => {
                options['enableOAuthImplicitGrant'] = true;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - disallow OAuth implicit grant flow for an application
        applicationCommand
            .command('disallow-implicit-grant')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Disallow OAuth implicit grant flow for an application')
            .action(async (id, options) => {
                options['enableOAuthImplicitGrant'] = false;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set name of an application
        applicationCommand
            .command('set-name')
            .argument('<id>', 'id of the application')
            .argument('<name>', 'name to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set name of an application')
            .action(async (id, name, options) => {
                options['name'] = name;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set origin for an applicatio
        applicationCommand
            .command('set-origin')
            .argument('<id>', 'id of the application')
            .argument('<origin>', 'origin to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set origin of an application')
            .action(async (id, origin, options) => {
                options['origin'] = origin;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set ownership of an application (to transfer ownership, requires app_superadmin or superadmin role OR
        // previous, new owners and current CLI user must be part of the same company)
        applicationCommand
            .command('set-ownership')
            .argument('<id>', 'id of the application')
            .argument('<ownerid>', 'id of the new owner')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set ownership of an application (to transfer ownership, requires app_superadmin or superadmin role OR previous, new owners and current CLI user must be part of the same company)')
            .action(async (id, ownerid, options) => {
                options['ownerId'] = ownerid;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // define a compatibility command for set-ownership to transfer ownership of an application (to transfer ownership, requires app_superadmin or superadmin role)
        applicationCommand
            .command('link')
            .argument('<id>', 'id of the application')
            .argument('<ownerid>', 'id of the new owner')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('[DEPRECATED] Set ownership of an application (use set-ownership command instead)')
            .action(async (id, ownerid, options) => {
                options['ownerId'] = ownerid;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - refresh application secret
        applicationCommand
            .command('refresh-secret')
            .argument('<id>', 'id of the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Refresh application secret (the old secret will no more be valid)')
            .action(async (id, options) => {
                options['refreshAppSecret'] = true;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set SSO authentication redirect URL on an application
        applicationCommand
            .command('set-sso-authentication-redirect-url')
            .argument('<id>', 'id of the application')
            .argument('<url>', 'URL of the SSO authentication redirect to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set SSO authentication redirect URL on an application')
            .action(async (id, url, options) => {
                options['ssoAuthenticationRedirectUrl'] = url;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set SSO logout redirect URL on an application
        applicationCommand
            .command('set-sso-logout-redirect-url')
            .argument('<id>', 'id of the application')
            .argument('<url>', 'URL of the SSO logout redirect to set on the application')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set SSO logout redirect URL on an application')
            .action(async (id, url, options) => {
                options['ssoLogoutRedirectUrl'] = url;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - set application type
        applicationCommand
            .command('set-type')
            .argument('<id>', 'id of the application')
            .argument('<type>', 'type to set on the application (web, mobile, desktop, server, bot, iot, admin)')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Set application type')
            .action(async (id, type, options) => {
                options['type'] = type;
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplication(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications push notification settings (pns) - get pns settings of an application. Can be filtered by type (apns_v2, fcm_v1) with --type option
        // the command accepts an optional pns id as argument to get a specific pns setting of the application
        // if pnsid is specified, the type option is ignored and the specific pns setting is returned, otherwise the type option can be used to filter the list of pns settings returned for the application
        const pnsCommand = applicationCommand
            .command('pns')
            .argument('<id>', 'id of the application')
            .argument('[pnsid]', 'id of the push notification setting (optional, if not provided, all push notification settings of the application will be returned)')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .addOption(new Option('--type <type>', 'Filter settings by type (apns_v2, fcm_v1)'))
            .description('Get push notification settings of an application')
            .action(async (id, pnsid, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.getApplicationPns(id, pnsid, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications push notification settings (pns) - create an application push notification setting
        // procotols supported are fcm_v1 and apns_v2
        pnsCommand
            .command('create')
            .argument('<id>', 'id of the application')
            .argument('<type>', 'type of the push notification setting to create (apns_v2, fcm_v1)')
            // option appBundleId for apns_v2 type
            .addOption(new Option('--appBundleId <appBundleId>', 'app bundle id (required for apns_v2 type)'))
            // option clientEmail for fcm_v1 type
            .addOption(new Option('--clientEmail <clientEmail>', 'client email (required for fcm_v1 type)'))
            // option isEnabled to enable or disable the push notification setting (defaults to enabled)
            .addOption(new Option('--isEnabled', 'enable the push notification setting (enabled by default)').default(true))
            // option privateKey for fcm_v1 type
            .addOption(new Option('--privateKey <privateKey>', 'private key (required for fcm_v1 type)'))
            // option privateKeyId for fcm_v1 type
            .addOption(new Option('--privateKeyId <privateKeyId>', 'private key id (required for fcm_v1 type)')) 
            // option projectId for fcm_v1 type
            .addOption(new Option('--projectId <projectId>', 'project id (required for fcm_v1 type)'))
            // option tokenUri for fcm_v1 type
            .addOption(new Option('--tokenUri <tokenUri>', 'token URI (required for fcm_v1 type)'))
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Create a push notification setting for an application')
            .action(async (id, type, options) => {
                options['type'] = type;
                PrettyPrint.withSpinner(
                    async () => { return await this.createApplicationPns(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications pns - delete an application push notification setting
        pnsCommand
            .command('delete')
            .argument('<id>', 'id of the application')
            .argument('<pnsid>', 'id of the push notification setting to delete')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Delete a push notification setting of an application')
            .action(async (id, pnsid, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.deleteApplicationPns(id, pnsid); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });
        
        pnsCommand
            .command('update')
            .argument('<id>', 'id of the application')
            .argument('<pnsid>', 'id of the push notification setting to update')
            // option appBundleId for apns_v2 type
//            .addOption(new Option('--appBundleId <appBundleId>', 'app bundle id (required for apns_v2 type)'))
            // option clientEmail for fcm_v1 type
            .addOption(new Option('--clientEmail <clientEmail>', 'client email (required for fcm_v1 type)'))
            // option isEnabled to enable or disable the push notification setting (defaults to enabled)
            .addOption(new Option('--isEnabled', 'enable the push notification setting (enabled by default)').default(true))
            // option privateKey for fcm_v1 type
            .addOption(new Option('--privateKey <privateKey>', 'private key (required for fcm_v1 type)'))
            // option privateKeyId for fcm_v1 type
            .addOption(new Option('--privateKeyId <privateKeyId>', 'private key id (required for fcm_v1 type)'))
            // option projectId for fcm_v1 type
            .addOption(new Option('--projectId <projectId>', 'project id (required for fcm_v1 type)'))
            // option tokenUri for fcm_v1 type
            .addOption(new Option('--tokenUri <tokenUri>', 'token URI (required for fcm_v1 type)'))
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Update a push notification setting of an application')
            .action(async (id, pnsid, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplicationPns(id, pnsid, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });


        // applications - thresholds -get application usage thresholds
        const thresholdsCommand = applicationCommand
            .command('thresholds')
            .argument('<id>', 'id of the application')
            // option groupName to specify the name of the threshold group to get thresholds for (if not specified, all threshold groups will be returned)
            .addOption(new Option('--groupName <groupName>', 'name of the threshold group to get thresholds for'))
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Get usage thresholds of an application')
            .action(async (id, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.getApplicationThresholds(id, options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });
        
        // applications - thresholds - create an usage threshold by group name for an application
        thresholdsCommand
            .command('create')
            .argument('<id>', 'id of the application')
            .argument('<groupName>', 'name of the threshold group to create a threshold for')
            .argument('<thresholdValue>', 'value of the threshold to create')
            .argument('<notification>', 'notification to send when the threshold is reached (mail, none)')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Create an usage threshold for an application by group name')
            .action(async (id, groupName, thresholdValue, notification, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.createApplicationThreshold(id, groupName, thresholdValue, notification); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - thresholds - delete an usage threshold by id for an application
        thresholdsCommand
            .command('delete')
            .argument('<id>', 'id of the application')
            .argument('<groupName>', 'name of the threshold group to delete thresholds for')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Delete an usage threshold of an application by threshold id')
            .action(async (id, groupName, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.deleteApplicationThreshold(id, groupName); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // applications - thresholds - update an usage threshold by id for an application
        thresholdsCommand
            .command('update')
            .argument('<id>', 'id of the application')
            .argument('<groupName>', 'name of the threshold group to update thresholds for')
            .argument('<thresholdValue>', 'value of the threshold to update')
            .argument('<notification>', 'notification to send when the threshold is reached (mail, none)')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .description('Update an usage threshold of an application by threshold id')
            .action(async (id, groupName, thresholdValue, notification, options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.updateApplicationThreshold(id, groupName, thresholdValue, notification); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        // dashboard
/*
        const dashboardCommand = program
            .command('dashboard');

        const dashboardApplicationsCommand = dashboardCommand
            .command('applications')
            .description('Display applications dashboard (Defaults to payasyougo offer dashboard)')
            .addOption(new Option('--owner <ownerid>', 'Get dashboard for an application owner'))
            .action(async (options) => {
                options['kpi'] = 'payasyougo';
                PrettyPrint.withSpinner(
                    async () => { return await this.dashboardApplications(options); },
                    (json) => { }
                );

            });

        dashboardApplicationsCommand
            .command('pay-as-you-go')
            .description('Display applications dashboard for pay-as-you-go offer')
            .addOption(new Option('--owner <ownerid>', 'Get dashboard for an application owner'))
            .action(async (options) => { options['kpi'] = 'payasyougo'; await this.dashboardApplications(options); });

        dashboardApplicationsCommand
            .command('business')
            .description('Display applications dashboard for business offer')
            .addOption(new Option('--owner <ownerid>', 'Get dashboard for an application owner'))
            .action(async (options) => { options['kpi'] = 'business'; await this.dashboardApplications(options); });

        dashboardApplicationsCommand
            .command('internal')
            .description('Display applications dashboard for internal offer')
            .addOption(new Option('--owner <ownerid>', 'Get dashboard for an application owner'))
            .action(async (options) => { options['kpi'] = 'internal'; await this.dashboardApplications(options); });
*/

        const developerApplicationsCommand = program
            .command('developer');

        developerApplicationsCommand
            .command('register')
            .description('Register current user as developer')
            .action(async (options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.developerRegister(options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        developerApplicationsCommand
            .command('unregister')
            .description('Unregister current user as developer (or another user with --loginEmail, requires admin role)')
            .addOption(new Option('--loginEmail <email>', 'email of the developer to unregister (requires admin role)'))
            .action(async (options) => {
                PrettyPrint.withSpinner(
                    async () => { return await this.developerUnregister(options); },
                    (json) => { PrettyPrint.display(json, undefined, options.json); }
                );
            });

        applicationCommand
            .command('about')
            .description('Get application portal information')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .action(async (options) => {
                PrettyPrint.withSpinner(
                    async () => this.about(),
                    (json) => PrettyPrint.display(json, undefined, options.json)
                );
            });

        applicationCommand
            .command('ping')
            .description('Check application portal health')
            .addOption(new Option('-j, --json', 'display raw JSON'))
            .action(async (options) => {
                PrettyPrint.withSpinner(
                    async () => this.ping(),
                    (json) => PrettyPrint.display(json, undefined, options.json)
                );
            });
    }

    async about() {
        this.logger.debug('Applications.about');
        return this.services['rest'].restCall('GET', '/api/rainbow/applications/v1.0/about');
    }

    async ping() {
        this.logger.debug('Applications.ping');
        return this.services['rest'].restCall('GET', '/api/rainbow/ping');
    }

    async getApplications(options) {
        this.logger.debug('Applications.getApplications');

        const limit = (options.limit ? Math.min(options.limit, 1000) : 25);
        const query_parameters = [
            ['limit', limit]
        ];

        if (options.page) {
            query_parameters.push(['offset', (options.page > 1 ? limit * (options.page - 1) : 0)])
        }

        if (options.notdeployed) {
            query_parameters.push(['env', 'not_deployed']);
        }
        if (options.indeployment) {
            query_parameters.push(['env', 'in_deployment']);
        }
        if (options.deployed) {
            query_parameters.push(['env', 'deployed']);
        }
        if (options.active) {
            query_parameters.push(['state', 'active']);
        }
        if (options.blocked) {
            query_parameters.push(['state', 'blocked']);
        }
        if (options.new) {
            query_parameters.push(['state', 'new']);
        }
        if (options.deleted) {
            query_parameters.push(['state', 'deleted']);
        }
        if (options.stopped) {
            query_parameters.push(['state', 'stopped']);
        }
        if (options.notactive) {
            query_parameters.push(['state', 'new, blocked, deleted, stopped']);
        }
        if (options.owner) {
            query_parameters.push(['ownerId', options.owner]);
        }
        if (options.own) {
            const myUserId = await this.services['enduser'].myUserId();
            query_parameters.push(['ownerId', myUserId]);
        }
        if (options.name) {
            query_parameters.push(['name', options.name]);
        }
        if (options.type) {
            query_parameters.push(['types', options.type]);
        }
        if (options.offer) {
            query_parameters.push(['kpi', options.offer]);
        }

        if (options.format) {
            query_parameters.push(['format', options.format]);
        }
        if (options.columns) {
            query_parameters.push(['format', 'full']);
        }

        const json = await this.services["rest"].restCall('GET', '/api/rainbow/applications/v1.0/applications', query_parameters);
        return json;
    }

    async getApplication(appid, options) {
        this.logger.debug('Applications.getApplication');

        const query_parameters = [];
        if (options.properties) {
            options.properties.forEach(pair => {
                const [key, value] = pair.split(":");
                // TODO: check errors, format
                query_parameters.push([key, value]);
            });
        }

        const json = await this.services["rest"].restCall('GET', `/api/rainbow/applications/v1.0/applications/${appid}`, query_parameters);
        var appData = json.data;
        if (appData.ownerId) {
            try {
                const ownerData = await this.services["admin"].getUser(appData.ownerId, { nooutput: true });
                if (ownerData) appData.owner = ownerData;
            }
            catch (error) {
                appData.owner = 'undefined'; // TODO how to warn the user that we could not get owner info ?
            }
        }
        return json;
    }

    async createApplication(name, options) {
        this.logger.debug('Applications.createApplication');

        const query_parameters = [];
        const body = {
            "name": name,
            "type": options.type,
            "kpi": options.offer,
            "activity": options.activity
        };

        if (options.owner) {
            body["ownerId"] = options.owner;
        }

        const json = await this.services["rest"].restCall('POST', '/api/rainbow/applications/v1.0/applications', query_parameters, body,
            { 'Accept': 'application/json', 'Content-Type': 'application/json' }
        );
        return json;
    }

    async deleteApplication(id, options) {
        this.logger.debug('Applications.deleteApplication');

        try {
            if (!options.force) {
                const rl = createInterface({
                    input: process.stdin,
                    output: process.stdout
                });
                const answer = await rl.question('Are you sure (Y/N) ?');
                const answerlow = answer.toLowerCase();
                if (answerlow !== 'y' && answerlow !== 'yes') {
                    PrettyPrint.error('Action cancelled');
                    rl.close();
                    return;
                }
                rl.close();
            }

            const json = await this.services['rest'].restCall('DELETE', `/api/rainbow/applications/v1.0/applications/${id}`, [], {},
                { 'Accept': 'application/json', 'Content-Type': 'application/json' });
            return json;
        }
        catch (error) {
            this.logger.error(error);
            throw error;
        }
    }

    async updateApplication(id, options) {
        this.logger.debug('Applications.updateApplication');

        const query_parameters = [];
        const body = {};
        if (options.name) {
            body['name'] = options.name;
        }
        if (options.type) {
            body['type'] = options.type;
        }
        if (options.ownerId) {
            body['ownerId'] = options.ownerId;
        }
        if (options.offer) {
            body['kpi'] = options.offer;
        }
        if (options.deployReason) {
            body['deployReason'] = options.deployReason;
        }
        if (options.description) {
            body['description'] = options.description;
        }
        if (options.allowSendingSms) {
            body['allowSendingSms'] = options.allowSendingSms;
        }
        if (options.allowRainbowEmbedding) {
            body['isAllowedToEmbed'] = options.allowRainbowEmbedding;
        }
        if (options.referrers) {
            this.logger.debug(options.referrers);
            body['referrers'] = options.referrers;
        }
        if (options.referals) {
            body['referals'] = options.referals;
        }
        if (options.appSuperadminComment) {
            body['appSuperadminComment'] = options.appSuperadminComment;
        }
        if (options.appSuperadminStatus) {
            body['appSuperadminStatus'] = options.appSuperadminStatus;
        }
        if (options.oauthRedirectUris) {
            body['oauthRedirectUris'] = options.oauthRedirectUris;
        }
        if (options.termsOfServiceUrl) {
            body['termsOfServiceUrl'] = options.termsOfServiceUrl;
        }
        if (options.privacyPoliciesUrl) {
            body['privacyPoliciesUrl'] = options.privacyPoliciesUrl;
        }
        if (options.activity) {
            body['activity'] = options.activity;
        }
        if (options.backchannelAuthenticationRedirectUri) {
            body['backchannelAuthenticationRedirectUri'] = options.backchannelAuthenticationRedirectUri;
        }
        if (options.disableOAuthConsentScreen !== undefined) {
            body['disableOAuthConsentScreen'] = options.disableOAuthConsentScreen;
        }
        if (options.enableOAuthAllowSeveralRefreshTokens !== undefined) {
            body['enableOAuthAllowSeveralRefreshTokens'] = options.enableOAuthAllowSeveralRefreshTokens;
        }
        if (options.enableOAuthImplicitGrant !== undefined) {
            body['enableOAuthImplicitGrant'] = options.enableOAuthImplicitGrant;
        }
        if (options.origin) {
            body['origin'] = options.origin;
        }
        if (options.refreshAppSecret) {
            body['refreshAppSecret'] = options.refreshAppSecret;
        }

        const json = await this.services["rest"].restCall('PUT', `/api/rainbow/applications/v1.0/applications/${id}`, query_parameters, body,
            { 'Accept': 'application/json', 'Content-Type': 'application/json' });
        return json;
    }

    /**
     * Perform an action on an application, changing its state
     * @param {string} action - Action to perform on application with id <id>. Can be 'request-deploy', 'deploy', 'approve', 'decline', 'block', 'unblock'.
     * @param {string} id - ApplicationId of the application
     * @param {Object} options - Command options 
     * @returns 
     */
    async actionApplication(action, id, options) {
        this.logger.debug('Applications.actionApplication');

        const body = {};
        if (action === 'deploy') {
            if (options.reason) {
                body['deployReason'] = options.reason;
            }
        }
        if (action === 'renew') {
            if (options.date) {
                body['expirationDate'] = options.date;
            }
        }

        const json = await this.services["rest"].restCall('PUT', `/api/rainbow/applications/v1.0/applications/${id}/${action}`, [], body,
            { 'Accept': 'application/json', 'Content-Type': 'application/json' });
        return json;
    }

    async getApplicationPns(id, pnsid, options) {
        this.logger.debug('Applications.getApplicationPns');

        const query_parameters = [];
        if (options.type) {
            query_parameters.push(['type', options.type]);
        }

        const json = await this.services["rest"].restCall('GET', `/api/rainbow/applications/v1.0/applications/${id}/push-settings/${pnsid?pnsid:""}`, query_parameters);
        return json;
    }

    async createApplicationPns(id, options) {
        this.logger.debug('Applications.createApplicationPns');

        const body = {
            'type': options.type
        };
        if (options.appBundleId) {
            body['appBundleId'] = options.appBundleId;
        }
        if (options.authorizationKey) {
            body['authorizationKey'] = options.authorizationKey;
        }
        if (options.certificateFile) {
            body['certificateFile'] = options.certificateFile;
        }
        if (options.certificateType) {
            body['certificateType'] = options.certificateType;
        }
        if (options.clientEmail) {
            body['client_email'] = options.clientEmail;
        }
        if (options.isEnabled !== undefined) {
            body['isEnabled'] = options.isEnabled;
        }
        if (options.privateKey) {
            body['private_key'] = options.privateKey;
        }
        if (options.projectId) {
            body['project_id'] = options.projectId;
        }
        if (options.senderId) {
            body['senderId'] = options.senderId;
        }
        if (options.tokenUri) {
            body['token_uri'] = options.tokenUri;
        }
        
        const json = await this.services["rest"].restCall('POST', `/api/rainbow/applications/v1.0/applications/${id}/push-settings`, [], body,
            { 'Accept': 'application/json', 'Content-Type': 'application/json' });
        return json;
    }

    async deleteApplicationPns(id, pnsid) {
        this.logger.debug('Applications.deleteApplicationPns');

        const json = await this.services["rest"].restCall('DELETE', `/api/rainbow/applications/v1.0/applications/${id}/push-settings/${pnsid}`);
        return json;
    }

    async updateApplicationPns(id, pnsid, options) {
        this.logger.debug('Applications.updateApplicationPns');

        const body = {};
        if (options.appBundleId) {
            body['appBundleId'] = options.appBundleId;
        }
        if (options.authorizationKey) {
            body['authorizationKey'] = options.authorizationKey;
        }
        if (options.certificateFile) {
            body['certificateFile'] = options.certificateFile;
        }
        if (options.certificateType) {
            body['certificateType'] = options.certificateType;
        }
        if (options.clientEmail) {
            body['client_email'] = options.clientEmail;
        }
        if (options.isEnabled !== undefined) {
            body['isEnabled'] = options.isEnabled;
        }
        if (options.privateKey) {
            body['private_key'] = options.privateKey;
        }
        if (options.projectId) {
            body['project_id'] = options.projectId;
        }
        if (options.senderId) {
            body['senderId'] = options.senderId;
        }
        if (options.tokenUri) {
            body['token_uri'] = options.tokenUri;
        }

        const json = await this.services["rest"].restCall('PUT', `/api/rainbow/applications/v1.0/applications/${id}/push-settings/${pnsid}`, [], body,
            { 'Accept': 'application/json', 'Content-Type': 'application/json' });
        return json;
    }  

    async getApplicationThresholds(id, options) {
        this.logger.debug('Applications.getApplicationThresholds');

        const json = await this.services["rest"].restCall('GET', `/api/rainbow/applications/v1.0/applications/${id}/thresholds${options.groupName?"/"+options.groupName:""}`);
        return json;
    }

    async createApplicationThreshold(id, groupName, thresholdValue, notification) {
        this.logger.debug('Applications.createApplicationThreshold');

        const body = {
            'threshold': thresholdValue,
            'notification': notification,
            'type': 'custom'
        };

        const json = await this.services["rest"].restCall('POST', `/api/rainbow/applications/v1.0/applications/${id}/thresholds/${groupName}`, [], body,
            { 'Accept': 'application/json', 'Content-Type': 'application/json' });
        return json;
    }

    async updateApplicationThreshold(id, groupName, thresholdValue, notification) {
        this.logger.debug('Applications.updateApplicationThreshold');

        const body = {
            'threshold': thresholdValue,
            'notification': notification
        };

        const json = await this.services["rest"].restCall('PUT', `/api/rainbow/applications/v1.0/applications/${id}/thresholds/${groupName}/custom`, [], body,
            { 'Accept': 'application/json', 'Content-Type': 'application/json' });
        return json;
    }

    async deleteApplicationThreshold(id, groupName) {
        this.logger.debug('Applications.deleteApplicationThreshold');

        const json = await this.services["rest"].restCall('DELETE', `/api/rainbow/applications/v1.0/applications/${id}/thresholds/${groupName}/custom`);
        return json;
    }

    async dashboardApplications(options) {
        this.logger.debug('Applications.dashboardApplications');

        const metricsGroups = await this.services['metrics'].apiGroupsMapping({ 'nooutput': true });
        const categories = [];
        console.log(metricsGroups.data);
        for (const group in metricsGroups.data) {
            const groupCategories = metricsGroups.data[group];
            for (const category in groupCategories) {
                categories.push(groupCategories[category]);
            }
        }
        console.log(categories);

        const app_options = {
            'nooutput': true,
            'format': 'full',
            'env': 'deployed',
            'limit': 1000,
            'offer': options.kpi,
            'env': 'deployed',
            'sortField': 'ownerId'
        };
        if (options.owner) {
            app_options['owner'] = options.owner;
        }

        const jsonApplications = await this.getApplications(app_options);
        const apps = jsonApplications.data;

        // Nom app, First Name Last Name owner, External (owner email), Deployment date, appID, stats API, est. cost
        const internalPatterns = ["al-enterprise.com", "al-mydemo.com", "cpaas.applicationid@provisoire.com"];
        const dash = [];
        for (const app of apps) {
            let isInternal = false;
            let owner = null;
            try {
                owner = await this.services['admin'].getUser(app.ownerId, { 'nooutput': true });
                isInternal = internalPatterns.some(pattern => {
                    return owner.data.loginEmail.includes(pattern);
                });
            }
            catch (error) {
                this.logger.error(`Cannot get owner info for application ${app.id}, error: ${error}`);
            }

            const metrics = await this.services['metrics'].getCPaaSMetrics(app.id, { 'nooutput': true, 'period': 'month' });
            console.log(metrics);

            const aggregatedApp = [];
            for (const metric of metrics.data) {
                const groups = metric.groupCounters;
                if (groups) {
                    for (const category in categories) {
                        if (!aggregatedApp.find((element) => { return element == aggregatedApp.group; })) {
                            aggregatedApp.push({
                                group: category,
                                count: 0
                            });
                        }

                    }
                }
            }

            dash.push({
                name: app.name,
                owner: owner ? `${owner.data.displayName}` : 'Unknown',
                external: !isInternal,
                deployed: app.dateOfDeployment,
                reason: app.deploymentReason,
                id: app.id
            });
        }
        console.table(dash);
    }

    async rainbowEmbedFrame(id, options) {
        this.logger.debug('Applications.rainbowEmbedFrame');

        const response = await this.services['rest'].get(`/api/rainbow/applications/v1.0/applications/${id}/embed`,
            [], { 'referrer': 'https://localhost:4443' }); // TODO referrer must be a parameter
        const html = await response.text();
        return html;
    }

    async developerRegister(options) {
        this.logger.debug('Applications.developerRegister');

        const query_parameters = [
            ['withoutConfirmationEmail', 'true']
        ];


        const json = await this.services["rest"].restCall('POST', '/api/rainbow/applications/v1.0/developers/register', query_parameters);
        return json;
    }

    async developerUnregister(options) {
        this.logger.debug('Applications.developerUnregister');

        if (options.loginEmail) {
            // loginEmail is a query parameter
            const query_parameters = [
                ['loginEmail', options.loginEmail]
            ];
            const json = await this.services["rest"].restCall('POST', '/api/rainbow/applications/v1.0/developers/unregister', query_parameters);
            return json;
        }

        const json = await this.services["rest"].restCall('POST', '/api/rainbow/applications/v1.0/developers/unregister');
        return json;
    }

    // ===== Deployment workflow =====

    async deployWorkflow(appid) {
        this.logger.debug('Applications.deployWorkflow');
        const { default: input } = await import('@inquirer/input'); const { default: select } = await import('@inquirer/select');

        // 1. Vérifier le rôle app_superadmin
        const roles = await this.services['enduser'].myRoles();
        if (!roles.includes('app_superadmin') && !roles.includes('superadmin')) {
            PrettyPrint.error('This command requires the app_superadmin or superadmin role.');
            return;
        }

        // 2. Demander l'id ou le nom si non fourni
        if (!appid) {
            appid = await input({ message: 'Application id or name:' });
        }
        if (!appid) {
            PrettyPrint.error('No application id or name provided.');
            return;
        }

        // 3. Résoudre l'application
        const app = await this._resolveApplication(appid, select);
        if (!app) return;

        // 4. Vérifier l'état (env)
        if (!['not_deployed', 'in_deployment'].includes(app.env)) {
            PrettyPrint.error(`Application env is '${app.env}'. Expected 'not_deployed' or 'in_deployment'.`);
            return;
        }

        // 5. Afficher les informations de l'application
        let ownerName = app.ownerId;
        let ownerCompanyId = null;
        let ownerCompanyName = null;
        let ownerCompanyIsBP = false;
        try {
            const ownerData = await this.services['admin'].getUser(app.ownerId, { nooutput: true });
            ownerName = ownerData?.data?.displayName || app.ownerId;
            ownerCompanyId = ownerData?.data?.companyId || null;
            if (ownerCompanyId) {
                const companyData = await this.services['admin'].getCompany(ownerCompanyId, {});
                ownerCompanyName = companyData?.data?.name || ownerCompanyId;
                ownerCompanyIsBP = companyData?.data?.isBP === true;
            }
        } catch (e) {
            this.logger.debug(`Could not fetch owner/company info: ${e}`);
        }

        console.log('');
        console.log(`  Application   : ${app.name}`);
        console.log(`  State (env)   : ${app.env}`);
        console.log(`  Owner id      : ${app.ownerId}`);
        console.log(`  Owner name    : ${ownerName}`);
        console.log(`  Company id    : ${ownerCompanyId || 'N/A'}`);
        console.log(`  Company name  : ${ownerCompanyName || 'N/A'}`);
        console.log(`  Company is BP : ${ownerCompanyIsBP ? 'yes' : 'no'}`);
        console.log('');

        // 6. Choisir l'offre
        const offerChoices = [{ name: 'business', value: 'business' }];
        if (ownerCompanyIsBP) {
            offerChoices.push({ name: 'demo', value: 'demo' });
        } else {
            console.log("  Note: 'demo' offer is not available (owner's company is not a BP).\n");
        }
        const offer = await select({
            message: 'Application offer:',
            choices: offerChoices
        });

        // 7. Si demo, demander la date d'expiration
        let expirationDate = null;
        if (offer === 'demo') {
            expirationDate = await input({
                message: 'Expiration date (YYYY-MM-DD):',
                validate: (v) => /^\d{4}-\d{2}-\d{2}$/.test(v) || 'Format must be YYYY-MM-DD'
            });
        }

        // 8. Demander la raison du déploiement
        let reason = '';
        while (!reason.trim()) {
            reason = await input({ message: 'Deployment reason:' });
        }

        // 9. Mettre à jour l'offre si elle diffère de l'offre actuelle
        if (app.kpi !== offer) {
            console.log(`\nSetting offer to '${offer}'...`);
            try {
                await this.updateApplication(app.id, { offer });
                console.log(`  → Offer updated.`);
            } catch (e) {
                PrettyPrint.error(`Failed to set offer: ${e}`);
                return;
            }
        }

        // 10. Si not_deployed : demander le déploiement (request-deploy)
        if (app.env === 'not_deployed') {
            console.log('\nRequesting deployment...');
            try {
                await this.actionApplication('request-deploy', app.id, {});
                console.log('  → Application is now in_deployment.');
            } catch (e) {
                PrettyPrint.error(`Failed to request deployment: ${e}`);
                return;
            }
        }

        // 11. Approuver le déploiement
        console.log('\nApproving deployment...');
        try {
            const deployOptions = { reason };
            if (expirationDate) deployOptions.date = expirationDate;
            const result = await this.actionApplication('deploy', app.id, deployOptions);

            // 11. Afficher l'état final
            const finalApp = result?.data;
            console.log('');
            PrettyPrint.success(`Application '${finalApp?.name || app.name}' deployed successfully.`);
            console.log(`  State (env)   : ${finalApp?.env || 'deployed'}`);
            console.log(`  Offer         : ${finalApp?.kpi || offer}`);
            if (offer === 'demo' && expirationDate) {
                console.log(`  Expiration    : ${finalApp?.expirationDate || expirationDate}`);
            }
            console.log('');
        } catch (e) {
            PrettyPrint.error(`Deployment failed: ${e}`);
        }
    }

    // Résout une application par id (32 hex chars) ou par nom (avec sélection interactive si plusieurs résultats).
    async _resolveApplication(appid, select) {
        const isObjectId = /^[0-9a-f]{32}$/i.test(appid);

        if (isObjectId) {
            try {
                const result = await this.getApplication(appid, {});
                return result?.data || null;
            } catch (e) {
                PrettyPrint.error(`Application not found with id: ${appid}`);
                return null;
            }
        }

        // Recherche par nom
        let apps;
        try {
            const result = await this.getApplications({ name: appid, limit: 100, format: 'full' });
            apps = result?.data || [];
        } catch (e) {
            PrettyPrint.error(`Error searching for application: ${e}`);
            return null;
        }

        if (apps.length === 0) {
            PrettyPrint.error(`No application found with name: ${appid}`);
            return null;
        }

        if (apps.length === 1) {
            return apps[0];
        }

        // Plusieurs résultats : enrichir avec owner + company puis proposer un choix
        const enriched = await Promise.all(apps.map(async (app) => {
            let ownerName = app.ownerId;
            let companyName = '';
            try {
                const ownerData = await this.services['admin'].getUser(app.ownerId, { nooutput: true });
                ownerName = ownerData?.data?.displayName || app.ownerId;
                const companyId = ownerData?.data?.companyId;
                if (companyId) {
                    const companyData = await this.services['admin'].getCompany(companyId, {});
                    companyName = companyData?.data?.name || companyId;
                }
            } catch (e) {
                this.logger.debug(`Could not fetch info for app ${app.id}: ${e}`);
            }
            return { app, ownerName, companyName };
        }));

        const chosen = await select({
            message: `${apps.length} applications found with name '${appid}'. Select one:`,
            choices: enriched.map(({ app, ownerName, companyName }) => ({
                name: `${app.id}  env:${app.env}  owner:${ownerName}  company:${companyName}`,
                value: app
            }))
        });

        return chosen;
    }
}