"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 os = __importStar(require("node:os"));
const nest_app_1 = require("../../lib/nest-app");
const context_banner_1 = require("../../lib/context-banner");
const nip_base_domain_util_1 = require("../../lib/nip-base-domain.util");
const cli_observability_cluster_service_1 = require("../../services/cli-observability-cluster.service");
const cli_ssh_service_1 = require("../../services/cli-ssh.service");
const encryption_service_1 = require("../../../../src/modules/shared/encryption/services/encryption.service");
const cluster_entity_1 = require("../../../../src/modules/infrastructure/clusters/entities/cluster.entity");
class EnvCredentials extends core_1.Command {
    redact(value, show) {
        if (!value)
            return '(not available)';
        return show ? value : '(hidden — pass --show-secrets to reveal)';
    }
    resolveStackPasswords(cluster, encryptionService, spinner) {
        const stackPasswords = cluster.metadata?.observabilityStack?.passwords;
        if (stackPasswords) {
            return {
                postgresPassword: stackPasswords.postgres,
                redisPassword: stackPasswords.redis,
                grafanaPassword: stackPasswords.grafana,
            };
        }
        const metadata = cluster.metadata;
        if (!metadata?.postgresPasswordEncrypted ||
            !metadata?.redisPasswordEncrypted ||
            !metadata?.grafanaPasswordEncrypted) {
            spinner.fail('Credentials not available in cluster metadata');
            console.log(chalk_1.default.yellow('\n⚠️  Cluster was created without storing credentials.\n'));
            return null;
        }
        try {
            return {
                postgresPassword: encryptionService.decrypt(metadata.postgresPasswordEncrypted),
                redisPassword: encryptionService.decrypt(metadata.redisPasswordEncrypted),
                grafanaPassword: encryptionService.decrypt(metadata.grafanaPasswordEncrypted),
            };
        }
        catch (error) {
            spinner.fail('Failed to decrypt credentials');
            console.log(chalk_1.default.red(`\n❌ Decryption error: ${error.message}\n`));
            return null;
        }
    }
    tryDecrypt(svc, value) {
        if (!value)
            return '';
        try {
            return svc.decrypt(value);
        }
        catch {
            return '';
        }
    }
    async runHealthCheck(observabilityService, masterIp, nipHostnameToken) {
        const s = (0, ora_1.default)('Testing connections...').start();
        const result = await observabilityService.checkObservabilityServices(masterIp, nipHostnameToken);
        s.succeed('Connection tests completed');
        return result;
    }
    async runSecretVerify(sshService, masterIp) {
        const s = (0, ora_1.default)('Verifying Kubernetes Secret on cluster...').start();
        return this.verifyKubernetesSecret(sshService, masterIp, s);
    }
    resolveZitadelSecrets(clusterMeta, encryptionService, baseDomain) {
        const authMode = clusterMeta?.authMode || 'local';
        const isZitadelMode = authMode === 'oidc';
        const domain = isZitadelMode
            ? clusterMeta?.zitadelDomain || `auth.${baseDomain}`
            : '';
        const empty = {
            domain,
            masterkey: '',
            dbAdminPassword: '',
            dbUserPassword: '',
            adminTempPassword: '',
        };
        if (!isZitadelMode || !clusterMeta?.zitadelMasterkeyEncrypted)
            return empty;
        try {
            return {
                domain,
                masterkey: encryptionService.decrypt(clusterMeta.zitadelMasterkeyEncrypted),
                dbAdminPassword: encryptionService.decrypt(clusterMeta.zitadelDbAdminPasswordEncrypted),
                dbUserPassword: encryptionService.decrypt(clusterMeta.zitadelDbUserPasswordEncrypted),
                adminTempPassword: clusterMeta.zitadelAdminTempPasswordEncrypted
                    ? encryptionService.decrypt(clusterMeta.zitadelAdminTempPasswordEncrypted)
                    : '',
            };
        }
        catch {
            return empty;
        }
    }
    async run() {
        const { flags } = await this.parse(EnvCredentials);
        (0, context_banner_1.printContextBanner)();
        let spinner = (0, ora_1.default)('Fetching credentials...').start();
        try {
            const app = await (0, nest_app_1.getNestApp)();
            const observabilityService = app.get(cli_observability_cluster_service_1.CliObservabilityClusterService);
            const encryptionService = app.get(encryption_service_1.EncryptionService);
            // Get observability cluster
            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 retrieve credentials.\n'));
                return;
            }
            spinner.succeed('Cluster found');
            // Get endpoints
            const endpoints = await observabilityService.getObservabilityEndpoints(cluster.id);
            const masterIp = cluster.masterIpAddress;
            if (!masterIp) {
                spinner.fail('Master IP address not available');
                return;
            }
            const stackPwResult = this.resolveStackPasswords(cluster, encryptionService, spinner);
            if (!stackPwResult)
                return;
            let { postgresPassword, redisPassword, grafanaPassword } = stackPwResult;
            // Read encryption key from ~/.flui/encryption.key (shared with API)
            const encryptionKeyPath = path.join(os.homedir(), '.flui', 'encryption.key');
            let encryptionKey = fs.existsSync(encryptionKeyPath)
                ? fs.readFileSync(encryptionKeyPath, 'utf-8').trim()
                : '';
            if (encryptionKey) {
                encryptionKey = this.redact(encryptionKey, flags['show-secrets']);
            }
            const clusterMeta = cluster.metadata;
            const baseDomain = (0, nip_base_domain_util_1.buildNipBaseDomain)(masterIp, cluster.nipHostnameToken);
            const zitadel = this.resolveZitadelSecrets(clusterMeta, encryptionService, baseDomain);
            let { masterkey: zitadelMasterkey, dbAdminPassword: zitadelDbAdminPassword, dbUserPassword: zitadelDbUserPassword, adminTempPassword: zitadelAdminTempPassword, } = zitadel;
            const { domain: zitadelDomain } = zitadel;
            const adminEmail = clusterMeta?.adminEmail || 'admin@flui.cloud';
            let adminPassword = this.tryDecrypt(encryptionService, clusterMeta?.adminPasswordEncrypted);
            const showSecrets = flags['show-secrets'];
            postgresPassword = this.redact(postgresPassword, showSecrets);
            redisPassword = this.redact(redisPassword, showSecrets);
            grafanaPassword = this.redact(grafanaPassword, showSecrets);
            adminPassword = adminPassword
                ? this.redact(adminPassword, showSecrets)
                : '';
            zitadelMasterkey = zitadelMasterkey
                ? this.redact(zitadelMasterkey, showSecrets)
                : '';
            zitadelDbAdminPassword = zitadelDbAdminPassword
                ? this.redact(zitadelDbAdminPassword, showSecrets)
                : '';
            zitadelDbUserPassword = zitadelDbUserPassword
                ? this.redact(zitadelDbUserPassword, showSecrets)
                : '';
            zitadelAdminTempPassword = zitadelAdminTempPassword
                ? this.redact(zitadelAdminTempPassword, showSecrets)
                : '';
            const healthStatus = flags.test
                ? await this.runHealthCheck(observabilityService, masterIp, cluster.nipHostnameToken)
                : null;
            const secretStatus = flags.verify
                ? await this.runSecretVerify(app.get(cli_ssh_service_1.CliSshService), masterIp)
                : null;
            // Output based on format
            if (flags.format === 'json') {
                const output = {
                    cluster: {
                        id: cluster.id,
                        name: cluster.name,
                        status: cluster.status,
                        masterIp,
                    },
                    endpoints,
                    credentials: {
                        admin: {
                            email: adminEmail,
                            password: adminPassword || '(not available)',
                            note: 'Bootstrap credentials — change after first login',
                        },
                        postgresql: {
                            host: masterIp,
                            port: 30432,
                            user: 'fluicloud',
                            database: 'fluicloud',
                            password: postgresPassword,
                        },
                        redis: {
                            host: masterIp,
                            port: 30379,
                            password: redisPassword,
                        },
                        grafana: {
                            url: endpoints.grafana,
                            username: 'admin',
                            password: grafanaPassword,
                        },
                        ...(zitadelDomain && {
                            zitadel: {
                                console: `https://${zitadelDomain}/ui/console`,
                                issuer: `https://${zitadelDomain}`,
                                audience: clusterMeta?.zitadelAudience ||
                                    '(configure after first setup)',
                                adminUsername: `flui-admin@zitadel.${zitadelDomain}`,
                                adminPassword: zitadelAdminTempPassword || '(not available)',
                                masterkey: zitadelMasterkey || '(not available)',
                                dbAdminPassword: zitadelDbAdminPassword || '(not available)',
                                dbUserPassword: zitadelDbUserPassword || '(not available)',
                            },
                        }),
                        ...(encryptionKey && { encryptionKey }),
                    },
                    connectionStrings: {
                        postgresql: `postgresql://fluicloud:${postgresPassword}@${masterIp}:30432/fluicloud`,
                        redis: `redis://:${redisPassword}@${masterIp}:30379`,
                    },
                    healthStatus,
                    secretStatus,
                };
                console.log(JSON.stringify(output, null, 2));
            }
            else {
                // Text format
                this.displayTextOutput(masterIp, endpoints, {
                    postgres: postgresPassword,
                    redis: redisPassword,
                    grafana: grafanaPassword,
                }, { email: adminEmail, password: adminPassword }, healthStatus, secretStatus, zitadelDomain
                    ? {
                        domain: zitadelDomain,
                        adminUsername: `flui-admin@zitadel.${zitadelDomain}`,
                        adminPassword: zitadelAdminTempPassword,
                        audience: clusterMeta?.zitadelAudience || '',
                        masterkey: zitadelMasterkey,
                        dbAdminPassword: zitadelDbAdminPassword,
                        dbUserPassword: zitadelDbUserPassword,
                    }
                    : null);
            }
        }
        catch (error) {
            spinner.fail('Error retrieving credentials');
            console.error(chalk_1.default.red(`\n❌ ${error.message}\n`));
            this.exit(1);
        }
        finally {
            await (0, nest_app_1.closeNestApp)();
        }
    }
    displayTextOutput(masterIp, endpoints, passwords, admin, healthStatus, secretStatus, zitadel = null) {
        console.log(chalk_1.default.cyan('\n📋 Observability Cluster Credentials'));
        console.log(chalk_1.default.cyan('━'.repeat(50)));
        // Endpoints section
        console.log(chalk_1.default.cyan('\n🌐 Endpoints:\n'));
        console.log(`   ${chalk_1.default.bold('Flui Web:')}   ${endpoints.fluiWeb || 'N/A'}`);
        console.log(`   ${chalk_1.default.bold('Flui API:')}   ${endpoints.fluiApi || 'N/A'}`);
        console.log(`   ${chalk_1.default.bold('Grafana:')}    ${endpoints.grafana || 'N/A'}`);
        console.log(`   ${chalk_1.default.bold('Prometheus:')} ${endpoints.prometheus || 'N/A'}`);
        console.log(`   ${chalk_1.default.bold('Loki:')}       ${endpoints.loki || 'N/A'}`);
        console.log(`   ${chalk_1.default.bold('PostgreSQL:')} ${endpoints.postgres || 'N/A'}`);
        console.log(`   ${chalk_1.default.bold('Redis:')}      ${endpoints.redis || 'N/A'}`);
        // Health status if tested
        if (healthStatus) {
            console.log(chalk_1.default.cyan('\n💚 Health Status:\n'));
            console.log(`   ${chalk_1.default.bold('Prometheus:')} ${this.formatHealth(healthStatus.prometheus)}`);
            console.log(`   ${chalk_1.default.bold('Loki:')}       ${this.formatHealth(healthStatus.loki)}`);
            console.log(`   ${chalk_1.default.bold('Grafana:')}    ${this.formatHealth(healthStatus.grafana)}`);
            console.log(`   ${chalk_1.default.bold('PostgreSQL:')} ${this.formatHealth(healthStatus.postgres)}`);
            console.log(`   ${chalk_1.default.bold('Redis:')}      ${this.formatHealth(healthStatus.redis)}`);
        }
        // Credentials section
        console.log(chalk_1.default.cyan('\n🔑 Credentials:\n'));
        console.log(`   ${chalk_1.default.bold('Flui API Admin:')}`);
        console.log(`     ${chalk_1.default.dim('Email:')}    ${admin.email}`);
        if (admin.password) {
            console.log(`     ${chalk_1.default.dim('Password:')} ${chalk_1.default.yellow(admin.password)}`);
            console.log(`     ${chalk_1.default.red('⚠️  Bootstrap credentials — change after first login')}`);
        }
        else {
            console.log(`     ${chalk_1.default.dim('Password:')} ${chalk_1.default.dim('(not available)')}`);
        }
        console.log(`\n   ${chalk_1.default.bold('PostgreSQL:')}`);
        console.log(`     ${chalk_1.default.dim('Host:')}     ${masterIp}`);
        console.log(`     ${chalk_1.default.dim('Port:')}     30432`);
        console.log(`     ${chalk_1.default.dim('User:')}     fluicloud`);
        console.log(`     ${chalk_1.default.dim('Database:')} fluicloud`);
        console.log(`     ${chalk_1.default.dim('Password:')} ${chalk_1.default.yellow(passwords.postgres)}`);
        console.log(`\n   ${chalk_1.default.bold('Redis:')}`);
        console.log(`     ${chalk_1.default.dim('Host:')}     ${masterIp}`);
        console.log(`     ${chalk_1.default.dim('Port:')}     30379`);
        console.log(`     ${chalk_1.default.dim('Password:')} ${chalk_1.default.yellow(passwords.redis)}`);
        console.log(`\n   ${chalk_1.default.bold('Grafana:')}`);
        console.log(`     ${chalk_1.default.dim('URL:')}      ${endpoints.grafana}`);
        console.log(`     ${chalk_1.default.dim('Username:')} admin`);
        console.log(`     ${chalk_1.default.dim('Password:')} ${chalk_1.default.yellow(passwords.grafana)}`);
        if (zitadel) {
            console.log(`\n   ${chalk_1.default.bold('Zitadel (Identity Provider):')}`);
            console.log(`     ${chalk_1.default.dim('Console:')}           https://${zitadel.domain}/ui/console`);
            console.log(`     ${chalk_1.default.dim('Admin Username:')}    ${zitadel.adminUsername}`);
            console.log(`     ${chalk_1.default.dim('Admin Password:')}    ${chalk_1.default.yellow(zitadel.adminPassword || 'N/A')}`);
            console.log(`     ${chalk_1.default.dim('Masterkey:')}         ${chalk_1.default.yellow(zitadel.masterkey || 'N/A')}`);
            console.log(`     ${chalk_1.default.dim('DB Admin Password:')} ${chalk_1.default.yellow(zitadel.dbAdminPassword || 'N/A')}`);
            console.log(`     ${chalk_1.default.dim('DB User Password:')}  ${chalk_1.default.yellow(zitadel.dbUserPassword || 'N/A')}`);
            console.log(`     ${chalk_1.default.dim('Audience (API):')}    ${chalk_1.default.dim(zitadel.audience || '(set after first Zitadel console setup)')}`);
        }
        // Connection strings section
        console.log(chalk_1.default.cyan('\n📝 Connection Strings:\n'));
        console.log(`   ${chalk_1.default.bold('PostgreSQL:')}`);
        const pgConn = chalk_1.default.dim(`postgresql://fluicloud:${passwords.postgres}@${masterIp}:30432/fluicloud`);
        console.log(`     ${pgConn}`);
        console.log(`\n   ${chalk_1.default.bold('Redis:')}`);
        const redisConn = chalk_1.default.dim(`redis://:${passwords.redis}@${masterIp}:30379`);
        console.log(`     ${redisConn}`);
        // Kubernetes Secret status if verified
        if (secretStatus) {
            console.log(chalk_1.default.cyan('\n🔐 Kubernetes Secret (flui-secrets):\n'));
            if (secretStatus.exists) {
                console.log(`   ${chalk_1.default.bold('Status:')}  ${chalk_1.default.green('✅ deployed')}`);
                console.log(`   ${chalk_1.default.bold('Keys:')}    ${secretStatus.keys.join(', ')}`);
                console.log(`   ${chalk_1.default.bold('SSH CA:')}  ${secretStatus.hasSshCa ? chalk_1.default.green('✅ configured') : chalk_1.default.yellow('⚠️  missing')}`);
            }
            else {
                console.log(`   ${chalk_1.default.bold('Status:')}  ${chalk_1.default.red('❌ not found')}`);
                console.log(chalk_1.default.dim('   Secret is created automatically during cluster setup.'));
                console.log(chalk_1.default.dim('   If missing, recreate the cluster or deploy manually.'));
            }
        }
        // Test commands section
        console.log(chalk_1.default.cyan('\n🧪 Test Commands:\n'));
        console.log(`   ${chalk_1.default.bold('PostgreSQL:')}`);
        const pgTest = chalk_1.default.dim(`PGPASSWORD='${passwords.postgres}' psql -h ${masterIp} -p 30432 -U fluicloud -d fluicloud`);
        console.log(`     ${pgTest}`);
        console.log(`\n   ${chalk_1.default.bold('Redis:')}`);
        const redisTest = chalk_1.default.dim(`redis-cli -h ${masterIp} -p 30379 -a '${passwords.redis}'`);
        console.log(`     ${redisTest}`);
        console.log(); // Empty line at the end
    }
    async verifyKubernetesSecret(sshService, masterIp, spinner) {
        try {
            const result = await sshService.sshExec(masterIp, "kubectl get secret flui-secrets -n default -o jsonpath='{.data}'");
            const data = JSON.parse(result);
            const keys = Object.keys(data);
            const hasSshCa = keys.includes('SSH_CA_PRIVATE_KEY') &&
                keys.includes('SSH_CA_PUBLIC_KEY');
            spinner.succeed('Kubernetes Secret verified');
            return { exists: true, keys, hasSshCa };
        }
        catch {
            spinner.warn('Kubernetes Secret not found or not accessible');
            return { exists: false, keys: [], hasSshCa: false };
        }
    }
    formatHealth(status) {
        if (status === 'healthy') {
            return `${chalk_1.default.green('✅ healthy')}`;
        }
        return `${chalk_1.default.red('❌ unreachable')}`;
    }
}
EnvCredentials.description = 'Display observability cluster connection information.\n' +
    'Secrets are hidden by default; pass --show-secrets to print them. To populate\n' +
    'a local .env.local for development use `flui dev creds` instead.';
EnvCredentials.examples = [
    '<%= config.bin %> <%= command.id %>',
    '<%= config.bin %> <%= command.id %> --format json',
    '<%= config.bin %> <%= command.id %> --test',
    '<%= config.bin %> <%= command.id %> --verify',
];
EnvCredentials.flags = {
    format: core_1.Flags.string({
        description: 'Output format (text or json)',
        options: ['text', 'json'],
        default: 'text',
    }),
    test: core_1.Flags.boolean({
        description: 'Test connections to all services',
        default: false,
    }),
    verify: core_1.Flags.boolean({
        description: 'Verify Kubernetes Secret (flui-secrets) is deployed on the cluster',
        default: false,
    }),
    'show-secrets': core_1.Flags.boolean({
        description: 'Print secret values in plaintext. Off by default — use `flui dev creds` to write secrets to .env.local instead.',
        default: false,
    }),
};
exports.default = EnvCredentials;
