"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@oclif/core");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const nest_app_1 = require("../../lib/nest-app");
const cli_observability_cluster_service_1 = require("../../services/cli-observability-cluster.service");
const cli_endpoint_resolver_service_1 = require("../../services/cli-endpoint-resolver.service");
const config_storage_1 = require("../../lib/config-storage");
const cluster_entity_1 = require("../../../../src/modules/infrastructure/clusters/entities/cluster.entity");
const env_file_1 = require("../../lib/utils/env-file");
const preferences_resolver_1 = require("../../config/preferences-resolver");
const preferences_echo_1 = require("../../config/preferences-echo");
const prompts_1 = require("../../lib/prompts");
const preferences_schema_1 = require("../../config/preferences-schema");
const context_banner_1 = require("../../lib/context-banner");
class EnvExportConfig extends core_1.Command {
    async run() {
        const { flags } = await this.parse(EnvExportConfig);
        (0, context_banner_1.printContextBanner)();
        let spinner = (0, ora_1.default)('Reading cluster configuration...').start();
        try {
            const app = await (0, nest_app_1.getNestApp)();
            const observabilityService = app.get(cli_observability_cluster_service_1.CliObservabilityClusterService);
            const resolver = app.get(cli_endpoint_resolver_service_1.CliEndpointResolverService);
            const cluster = await observabilityService.getObservabilityCluster();
            if (!cluster) {
                spinner.fail('No observability cluster found');
                console.log(chalk_1.default.yellow('\n⚠️  No observability cluster exists.\n'));
                console.log(chalk_1.default.dim('Create one with:'));
                console.log(`   ${chalk_1.default.cyan('flui env create')}\n`);
                return;
            }
            if (cluster.status !== cluster_entity_1.ClusterStatus.READY) {
                spinner.fail(`Cluster is not ready (status: ${cluster.status})`);
                console.log(chalk_1.default.yellow('\n⚠️  Cluster must be in READY status to export configuration.\n'));
                return;
            }
            const masterIp = cluster.masterIpAddress;
            if (!masterIp) {
                spinner.fail('Master IP address not available');
                return;
            }
            spinner.succeed('Cluster configuration loaded');
            spinner = (0, ora_1.default)('Resolving endpoints from cluster...').start();
            const endpoints = await resolver.resolveEndpoints(masterIp, cluster.nipHostnameToken);
            spinner.succeed('Endpoints resolved');
            // ADMIN_EMAIL and SSH_CA_PUBLIC_KEY live in flui-secrets but reading
            // them here would require kube-API access (closed by the new firewall
            // policy). They are written to .env.local by `flui dev creds`, which
            // reaches the secret over SSH+kubectl. Here we fall back to the
            // 'email' preference and skip the public CA key.
            const adminEmail = '';
            const sshCaPublicKey = '';
            const authMode = endpoints.authMode === 'unknown' ? 'local' : endpoints.authMode;
            const resolvedIssuer = this.resolveIssuer(endpoints);
            const resolvedJwks = this.resolveJwks(endpoints, resolvedIssuer);
            // Display configuration
            console.log(chalk_1.default.cyan('\n📋 Exporting Cluster Configuration\n'));
            console.log(`   ${chalk_1.default.bold('Cluster:')}    ${cluster.name}`);
            console.log(`   ${chalk_1.default.bold('Master IP:')}  ${masterIp}`);
            console.log(`   ${chalk_1.default.bold('Status:')}     ${cluster.status}\n`);
            console.log(chalk_1.default.cyan('⚙️  Configuration to export (endpoints only):\n'));
            console.log(`   DB_HOST=127.0.0.1   ${chalk_1.default.dim('# via `flui dev tunnel`')}`);
            console.log(`   DB_PORT=5432`);
            console.log(`   DB_USERNAME=fluicloud`);
            console.log(`   DB_NAME=fluicloud`);
            console.log(`   REDIS_HOST=127.0.0.1`);
            console.log(`   REDIS_PORT=6379`);
            console.log(`   KUBECONFIG_SERVER_OVERRIDE=https://127.0.0.1:6443`);
            console.log(`   GRAFANA_URL=http://localhost:3001        ${chalk_1.default.dim('# via `flui dev tunnel --ports grafana`')}`);
            console.log(`   PROMETHEUS_ENDPOINT=http://localhost:9090 ${chalk_1.default.dim('# via `flui dev tunnel --ports vmsingle`')}`);
            console.log(`   LOKI_ENDPOINT=http://localhost:3100       ${chalk_1.default.dim('# via `flui dev tunnel --ports loki`')}`);
            if (resolvedIssuer || authMode === 'oidc') {
                console.log(`   OIDC_ISSUER=${resolvedIssuer || chalk_1.default.dim('(not set)')}`);
                console.log(`   OIDC_JWKS_URI=${resolvedJwks || chalk_1.default.dim('(not set)')}`);
                console.log(`   OIDC_AUDIENCE=${endpoints.oidcAudience || chalk_1.default.dim('(set after first Zitadel setup)')}`);
            }
            console.log(`   AUTH_MODE=${authMode}`);
            console.log(`   ADMIN_EMAIL=${adminEmail || chalk_1.default.dim('(not set)')}`);
            console.log(`   SSH_CA_PUBLIC_KEY=${sshCaPublicKey ? chalk_1.default.green('(present)') : chalk_1.default.dim('(not set)')}`);
            console.log(chalk_1.default.dim('\n   Secrets (DB/Redis/JWT/SSH CA private/encryption key) are NOT written here.\n' +
                '   Run `flui dev creds` to populate them in .env.local.\n'));
            if (flags['dry-run']) {
                console.log(chalk_1.default.yellow('🔍 Dry run mode - no changes will be made\n'));
                return;
            }
            if (!flags.force) {
                console.log(chalk_1.default.yellow('⚠️  This will update your .env file.\n'));
                console.log(chalk_1.default.dim('Use --force to skip this check in automated scripts.\n'));
            }
            // Resolve user preferences once and surface them before any write happens.
            const preferences = await this.resolvePreferences({
                emailFlag: flags.email,
                apiPathFlag: flags['api-path'],
                dashboardPathFlag: flags['dashboard-path'],
                certificateModeFlag: flags['certificate-mode'],
                save: flags.save,
                // Skip the dashboardPath/certificateMode prompts entirely when the user opted out.
                skipDashboard: flags['no-dashboard'],
            });
            spinner = (0, ora_1.default)('Updating .env file...').start();
            // .env lives next to the resolved apiPath. Defaults to '.' (cwd) so legacy
            // invocations from inside flui.api keep working without any preference set.
            const apiDir = path.isAbsolute(preferences.apiPath)
                ? preferences.apiPath
                : path.resolve(process.cwd(), preferences.apiPath);
            const envPath = path.join(apiDir, '.env');
            const envExamplePath = path.join(apiDir, '.env.example');
            let envContent = '';
            if (fs.existsSync(envPath)) {
                envContent = fs.readFileSync(envPath, 'utf-8');
            }
            else if (fs.existsSync(envExamplePath)) {
                envContent = fs.readFileSync(envExamplePath, 'utf-8');
                spinner.info('.env not found, using .env.example as template');
            }
            else {
                spinner.warn('No .env or .env.example found, creating new .env');
            }
            if (flags.backup && fs.existsSync(envPath)) {
                const timestamp = new Date()
                    .toISOString()
                    .replaceAll(':', '-')
                    .replace(/\..+/, '');
                const backupPath = `${envPath}.backup.${timestamp}`;
                fs.copyFileSync(envPath, backupPath);
                spinner.succeed(`Backup created: ${path.basename(backupPath)}`);
            }
            const envVars = {
                KUBECONFIG_SERVER_OVERRIDE: 'https://127.0.0.1:6443',
                DB_HOST: '127.0.0.1',
                DB_PORT: '5432',
                DB_USERNAME: 'fluicloud',
                DB_NAME: 'fluicloud',
                REDIS_HOST: '127.0.0.1',
                REDIS_PORT: '6379',
                GRAFANA_URL: 'http://localhost:3001',
                PROMETHEUS_ENDPOINT: 'http://localhost:9090',
                LOKI_ENDPOINT: 'http://localhost:3100',
                AUTH_MODE: authMode,
            };
            if (resolvedIssuer)
                envVars.OIDC_ISSUER = resolvedIssuer;
            if (resolvedJwks)
                envVars.OIDC_JWKS_URI = resolvedJwks;
            if (endpoints.oidcAudience)
                envVars.OIDC_AUDIENCE = endpoints.oidcAudience;
            // ADMIN_EMAIL: prefer the value baked into the cluster's flui-secrets, fall back to
            // the resolved 'email' preference. Either way the API gets a populated value.
            const resolvedEmail = adminEmail || preferences.email;
            if (resolvedEmail)
                envVars.ADMIN_EMAIL = resolvedEmail;
            if (sshCaPublicKey)
                envVars.SSH_CA_PUBLIC_KEY = sshCaPublicKey;
            const updatedEnv = (0, env_file_1.updateEnvContent)(envContent, envVars);
            fs.writeFileSync(envPath, updatedEnv, 'utf-8');
            spinner.succeed('.env file updated successfully');
            if (!flags['no-dashboard']) {
                await this.syncDashboardConfig({
                    dashboardPath: preferences.dashboardPath,
                    certificateMode: preferences.certificateMode,
                    authMode,
                    oidcIssuer: resolvedIssuer,
                    oidcClientId: endpoints.oidcClientId,
                    oidcAudience: endpoints.oidcAudience,
                    backup: flags.backup,
                });
            }
            console.log(chalk_1.default.green('\n✅ Endpoint configuration exported.\n'));
            console.log(chalk_1.default.dim('Next steps to run the API locally:'));
            console.log(`   1. ${chalk_1.default.cyan('flui dev creds')}    ${chalk_1.default.dim('# write secrets to .env.local')}`);
            console.log(`   2. ${chalk_1.default.cyan('flui dev tunnel')}   ${chalk_1.default.dim('# expose cluster services on localhost')}`);
            console.log(`   3. ${chalk_1.default.cyan('pnpm run start:dev')}\n`);
        }
        catch (error) {
            spinner.fail('Error exporting configuration');
            console.error(chalk_1.default.red(`\n❌ ${error.message}\n`));
            this.exit(1);
        }
        finally {
            await (0, nest_app_1.closeNestApp)();
        }
    }
    /**
     * Resolve every preference this command consumes (email, dashboardPath, certificateMode)
     * through the layered config: flag > env > project-local > user > default > prompt.
     *
     * Prompted values are persisted to the active profile only when --save is set;
     * otherwise they are used for this run and forgotten (no surprise side effects).
     *
     * `skipDashboard` shortcuts the dashboard-only preferences when --no-dashboard is set.
     */
    async resolvePreferences(opts) {
        const storage = new config_storage_1.ConfigStorage();
        const resolver = new preferences_resolver_1.PreferencesResolver(storage);
        const echoEntries = [];
        const out = {};
        const resolveOne = async (key, explicit) => {
            const initial = resolver.resolve(key, explicit);
            if (initial.value !== null) {
                echoEntries.push(initial);
                return initial.value;
            }
            const prompted = await this.promptForPreference(key);
            if (opts.save) {
                storage.setPreference(key, prompted);
            }
            echoEntries.push({
                key,
                value: prompted,
                source: opts.save ? 'user' : 'explicit',
            });
            return prompted;
        };
        out.email = await resolveOne('email', opts.emailFlag);
        out.apiPath = await resolveOne('apiPath', opts.apiPathFlag);
        if (!opts.skipDashboard) {
            out.dashboardPath = await resolveOne('dashboardPath', opts.dashboardPathFlag);
            out.certificateMode = await resolveOne('certificateMode', opts.certificateModeFlag);
        }
        (0, preferences_echo_1.echoPreferences)(echoEntries, resolver);
        if (!opts.save &&
            echoEntries.some((e) => e.source === 'explicit' || e.source === 'default')) {
            console.log(chalk_1.default.dim('  ↳ Pass --save to persist prompted/default values to ~/.flui/profiles/<active>/config.json\n'));
        }
        return out;
    }
    /**
     * Patch the dashboard's config.json with the localhost API endpoints + cluster auth +
     * the resolved certificateMode. Caller is responsible for resolving inputs.
     */
    async syncDashboardConfig(opts) {
        const dashboardPath = opts.dashboardPath;
        const certificateMode = opts.certificateMode;
        const absoluteDashboardPath = path.isAbsolute(dashboardPath)
            ? dashboardPath
            : path.resolve(process.cwd(), dashboardPath);
        const configPath = path.join(absoluteDashboardPath, 'src', 'assets', 'config.json');
        const spinner = (0, ora_1.default)(`Updating dashboard config (${configPath})...`).start();
        if (!fs.existsSync(configPath)) {
            spinner.warn(`Dashboard config not found at ${configPath} — skipping. Adjust dashboardPath or pass --no-dashboard.`);
            return;
        }
        let current = {};
        try {
            current = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
        }
        catch (err) {
            spinner.fail(`Dashboard config.json is not valid JSON (${err.message}) — skipping`);
            return;
        }
        if (opts.backup) {
            const timestamp = new Date()
                .toISOString()
                .replaceAll(':', '-')
                .replace(/\..+/, '');
            fs.copyFileSync(configPath, `${configPath}.backup.${timestamp}`);
        }
        const updated = {
            ...current,
            apiBaseUrl: 'http://localhost:3000',
            wsUrl: 'ws://localhost:3000',
            authMode: opts.authMode,
            oidcIssuer: opts.oidcIssuer || current.oidcIssuer || '',
            oidcClientId: opts.oidcClientId || current.oidcClientId || '',
            oidcAudience: opts.oidcAudience || current.oidcAudience || '',
            certificateMode,
        };
        fs.writeFileSync(configPath, JSON.stringify(updated, null, 2) + '\n', 'utf-8');
        spinner.succeed(`Dashboard config updated (authMode=${opts.authMode}, certificateMode=${certificateMode}, oidcClientId=${opts.oidcClientId ? 'set' : '(empty)'})`);
    }
    /**
     * Prompt for a missing preference value, validating against the schema.
     * Used only when every other layer (flag, env, project, user, default) failed to provide a value.
     */
    async promptForPreference(key) {
        const def = preferences_schema_1.PREFERENCES[key];
        return (0, prompts_1.promptInput)({
            message: def.description,
            default: def.defaultValue,
            validate: (v) => preferences_resolver_1.PreferencesResolver.validate(key, v),
        });
    }
    resolveIssuer(endpoints) {
        if (endpoints.zitadel.fqdn)
            return `https://${endpoints.zitadel.fqdn}`;
        if (endpoints.oidcIssuer)
            return endpoints.oidcIssuer;
        return '';
    }
    resolveJwks(endpoints, issuer) {
        const raw = endpoints.oidcJwksUri;
        const isInCluster = raw.includes('.svc.cluster.local');
        if (raw && !isInCluster)
            return raw;
        if (issuer)
            return `${issuer.replace(/\/$/, '')}/oauth/v2/keys`;
        return '';
    }
}
EnvExportConfig.description = 'Export cluster endpoints (and non-sensitive defaults) to .env for running the API locally.\n' +
    'Secrets are NOT written by this command — run `flui dev creds` for those, and `flui dev tunnel`\n' +
    'to expose Postgres/Redis/etc. on localhost.';
EnvExportConfig.examples = [
    '<%= config.bin %> <%= command.id %>',
    '<%= config.bin %> <%= command.id %> --dry-run',
    '<%= config.bin %> <%= command.id %> --no-backup',
];
EnvExportConfig.flags = {
    'dry-run': core_1.Flags.boolean({
        description: 'Show what would be changed without modifying .env',
        default: false,
    }),
    backup: core_1.Flags.boolean({
        description: 'Create backup of existing .env file',
        default: true,
        allowNo: true,
    }),
    force: core_1.Flags.boolean({
        description: 'Skip confirmation prompt',
        default: false,
    }),
    'no-dashboard': core_1.Flags.boolean({
        description: 'Skip updating the dashboard config.json',
        default: false,
    }),
    // Direct overrides for the layered preferences resolver.
    // Without these, values resolve from env > project file > user config > prompt.
    'api-path': core_1.Flags.string({
        description: `Override resolved value for the "apiPath" preference (${preferences_schema_1.PREFERENCES.apiPath.description})`,
    }),
    'dashboard-path': core_1.Flags.string({
        description: `Override resolved value for the "dashboardPath" preference (${preferences_schema_1.PREFERENCES.dashboardPath.description})`,
    }),
    'certificate-mode': core_1.Flags.string({
        description: `Override resolved value for the "certificateMode" preference (one of: ${preferences_schema_1.PREFERENCES.certificateMode.allowedValues.join(', ')})`,
        options: [...preferences_schema_1.PREFERENCES.certificateMode.allowedValues],
    }),
    email: core_1.Flags.string({
        description: `Override resolved value for the "email" preference (${preferences_schema_1.PREFERENCES.email.description})`,
    }),
    save: core_1.Flags.boolean({
        description: 'Persist any value entered at the interactive prompt to the active profile (~/.flui/profiles/<active>/config.json). Without this flag, prompted values are used only for this run.',
        default: false,
    }),
};
exports.default = EnvExportConfig;
