#!/usr/bin/env node

// Ctrl+C sur un prompt @inquirer produit un ExitPromptError via signal-exit,
// rejeté en contexte synchrone avant que le catch async puisse l'attraper.
process.on('uncaughtException', (err) => { if (err?.name === 'ExitPromptError') process.exit(0); console.error(err); process.exit(1); });
process.on('unhandledRejection', (err) => { if (err?.name === 'ExitPromptError') process.exit(0); });

import { Command, Option } from 'commander';
import fs from 'fs';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'url';

import { Logger } from './util/logger.js';

import { Admin } from './services/admin.js';
import { Applications } from './services/applications.js';
import { Rest } from './rest.js';
import { Authentication } from './services/authentication.js';
import { EndUser } from './services/enduser.js';
import { ConfigManager } from './config/configManager.js';
import { Metrics } from './services/metrics.js';
import { InteractiveDisplay } from './output/interactive_display.js';
import { Telemetry } from './util/telemetry.js';
import { MassPro } from './services/masspro.js';
import { WHD } from './services/whd.js';
import { Workflows } from './services/workflows.js';
import { ApiDocs } from './services/apidocs.js';
import { Guides } from './services/guides.js';
import { Docs } from './services/docs.js';
import { Subscription } from './services/subscription.js';

(async () => {
    try {
        const logger = new Logger();

        logger.debug('starting');

        const program = new Command();
        const _pkg         = JSON.parse(readFileSync(new URL('./package.json', import.meta.url)));
        const _pkgVersion  = _pkg.version;
        const _hasDebug    = fs.existsSync(fileURLToPath(new URL('./services/debug.js',    import.meta.url)));
        const _hasInternal = fs.existsSync(fileURLToPath(new URL('./services/internal.js', import.meta.url)));
        const _buildVariant = _hasDebug ? 'dev' : _hasInternal ? 'internal' : null;
        const _versionString = _buildVariant ? `${_pkgVersion} (${_buildVariant})` : _pkgVersion;

        program
            .name('rainbow-cli-ng')
            .description('Rainbow command line interface')
            .version(_versionString)
            .addOption(new Option('--debug', 'output debug information'))
            .addOption(new Option('--log <file>', 'output logs to file'))
            .addOption(new Option('--profile <name>', 'use a specific profile for this command (without changing the active profile)'))
            .hook('preAction', (thisCommand, actionCommand) => {
                const options = thisCommand.opts();
                if (options.log) {
                    logger.enableFileLogging(options.log);
                }
                // Réinitialiser à chaque commande : en mode shell, l'état persiste entre
                // les parseAsync successifs et --debug ne doit affecter qu'une seule commande.
                logger.level = options.debug ? 'debug' : 'error';
            })
            .addOption(new Option('--verbose', 'enable verbose output'))
            .configureHelp({
                sortSubcommands: true
            });


        const services = [];
        const configManager = new ConfigManager(program, logger, services);
        await configManager.load();

        // Appliquer --profile tôt (avant program.parse)
        // car le preAction hook ne tourne qu'après program.parse().
        const _profileFlagIdx = process.argv.findIndex(a => a === '--profile');
        if (_profileFlagIdx !== -1) {
            const _profileName = process.argv[_profileFlagIdx + 1];
            if (!_profileName) {
                console.error('--profile requires a profile name.');
                process.exit(1);
            }
            try {
                configManager.useTemporaryProfile(_profileName);
            } catch (err) {
                console.error(err.message);
                process.exit(1);
            }
        }

        const rest = new Rest(program, logger, services);
        const admin = new Admin(program, logger, services);
        const applications = new Applications(program, logger, services);
        const authentication = new Authentication(program, logger, services);
        const enduser = new EndUser(program, logger, services);
        const metrics = new Metrics(program, logger, services);
        const interactive_display = new InteractiveDisplay(program, logger, services);
        const masspro = new MassPro(program, logger, services);
        const telemetry = new Telemetry(program, logger, services, { telemetryEndpoint: 'http://localhost:3001/api/telemetry', userId: 'test', enabled: false }); // TODO generate unique id
        const whd = new WHD(program, logger, services);
        const workflows = new Workflows(program, logger, services);
        const guides = new Guides(program, logger, services);
        new Docs(program, logger, services);
        new Subscription(program, logger, services);

        // Charger la documentation API (OpenAPI specs) pour la commande "ask" et "api-docs"
        const apidocs = new ApiDocs(program, logger, services);
        const docsDir = fileURLToPath(new URL('./api-docs/', import.meta.url));
        await apidocs.load(docsDir);

        // Charger les guides développeur (Markdown) pour la commande "guides"
        const guidesDir = fileURLToPath(new URL('./guides/', import.meta.url));
        await guides.load(guidesDir);

        // Services expérimentaux — chargés uniquement si présents dans ce build
        const _expSuffix = (name) => fileURLToPath(new URL(`./services/${name}`, import.meta.url));
        if (fs.existsSync(_expSuffix('ai.js'))) {
            const { AI } = await import('./services/ai.js');
            new AI(program, logger, services);
        }
        if (fs.existsSync(_expSuffix('webinar.js'))) {
            const { Webinar } = await import('./services/webinar.js');
            new Webinar(program, logger, services);
        }
        if (fs.existsSync(_expSuffix('analytics.js'))) {
            const { Analytics } = await import('./services/analytics.js');
            new Analytics(program, logger, services);
        }
        if (fs.existsSync(_expSuffix('ngcpprovisioning.js'))) {
            const { Ngcpprovisioning } = await import('./services/ngcpprovisioning.js');
            new Ngcpprovisioning(program, logger, services);
        }
        if (fs.existsSync(_expSuffix('roomadmin.js'))) {
            const { RoomAdmin } = await import('./services/roomadmin.js');
            new RoomAdmin(program, logger, services);
        }
        if (fs.existsSync(_expSuffix('ucs.js'))) {
            const { UCS } = await import('./services/ucs.js');
            new UCS(program, logger, services);
        }
        if (fs.existsSync(_expSuffix('webhook_listener.js'))) {
            const { WebhookListener } = await import('./services/webhook_listener.js');
            new WebhookListener(program, logger, services);
        }
        if (fs.existsSync(_expSuffix('applications-experimental.js'))) {
            const { registerExperimentalApplicationCommands } = await import('./services/applications-experimental.js');
            registerExperimentalApplicationCommands(applications.applicationCommand, applications);
        }

        // Debug service is only included in non-production builds
        if (fs.existsSync('./services/debug.js')) {
            const { Debug } = await import('./services/debug.js');
            const debug = new Debug(program, logger, services);
        }

        // Commandes internes — chargées si services/internal.js est présent dans ce build
        if (fs.existsSync(fileURLToPath(new URL('./services/internal.js', import.meta.url)))) {
            const { Internal }      = await import('./services/internal.js');
            const { Dashboard }     = await import('./services/dashboard.js');
            const { Provisioning }  = await import('./services/provisioning.js');
            new Internal(program, logger, services);
            new Dashboard(program, logger, services);
            new Provisioning(program, logger, services);
        }

        // Construit le chemin qualifie d'une commande en remontant l'arbre parent (ex: "config:create-profile")
        const getCommandPath = (cmd) => {
            const parts = [];
            let c = cmd;
            while (c && c.parent) { parts.unshift(c.name()); c = c.parent; }
            return parts.join(':');
        };

        program
            .hook('preAction', async (thisCommand, actionCommand) => {
                // En mode shell, --profile par commande est traité ici (process.argv ne change pas)
                if (services['_shellMode']) {
                    const profileOverride = thisCommand.opts().profile;
                    if (profileOverride) {
                        try {
                            configManager.useTemporaryProfile(profileOverride);
                            services['_shellProfileOverride'] = true; // pour restauration dans _execCommand
                        } catch (err) {
                            throw new Error(err.message);
                        }
                    }
                }

                const bypassCommands = ['config:create-profile', 'config:set-profile', 'config:show-profile', 'config:set-ai-key',
                    'api-docs:search', 'api-docs:portals', 'api-docs:endpoint', 'api-docs:update', 'api-docs:browse',
                    'guides:list', 'guides:search', 'guides:show', 'guides:browse'];
                if (bypassCommands.includes(getCommandPath(actionCommand))) {
                    return;
                }

                const isReady = await configManager.isConfigured();
                if (!isReady) {
                    const msg = "CLI is not configured. Please run 'rbw config create-profile' to set up your configuration.";
                    if (services['_shellMode']) throw new Error(msg);
                    console.error(msg);
                    process.exit(1);
                }
            });

        // Commande run — exécuter un script .js ou .rbw sans entrer en mode shell
        program
            .command('run <script>')
            .description('Execute a .js or .rbw script file without entering shell mode')
            .action(async (scriptFile) => {
                const { Shell } = await import('./services/shell.js');
                const shell = new Shell(program, logger, services);
                shell._baseProfile = configManager.config.currentProfile;
                shell._vars.set('profile', configManager.config.currentProfile);
                const profileData = configManager.config.profiles[configManager.config.currentProfile];
                if (profileData?.host) shell._vars.set('host', profileData.host);
                await shell._runScript(scriptFile);
                process.exit(0);
            });

        // Mode shell : aucun argument positionnel et pas de --help/--version → REPL interactif
        const _argv = process.argv.slice(2);
        const positionalArgs = _argv.filter(a => !a.startsWith('-'));
        const isHelpOrVersion = _argv.some(a => a === '--help' || a === '-h' || a === '--version' || a === '-V');
        if (positionalArgs.length === 0 && !isHelpOrVersion) {
            const { Shell } = await import('./services/shell.js');
            await new Shell(program, logger, services).start();
            return;
        }

        // Notification de mise à jour (vérification en arrière-plan, affichée au lancement suivant)
        // Sauté en mode dev (build local) pour ne pas polluer le workflow de développement.
        if (_buildVariant !== 'dev') {
            const { default: updateNotifier } = await import('update-notifier');
            updateNotifier({ pkg: { name: _pkg.name, version: _pkgVersion } }).notify();
        }

        program.parse();
    }
    catch (error) {
        if (error?.name === 'ExitPromptError') process.exit(0);
        console.error(error);
    }
})();
