"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 cli_observability_cluster_service_1 = require("../../services/cli-observability-cluster.service");
const cli_ssh_service_1 = require("../../services/cli-ssh.service");
const config_storage_1 = require("../../lib/config-storage");
const encryption_service_1 = require("../../../../src/modules/shared/encryption/services/encryption.service");
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 prompts_1 = require("../../lib/prompts");
const preferences_schema_1 = require("../../config/preferences-schema");
class DevCreds extends core_1.Command {
    async run() {
        const { flags } = await this.parse(DevCreds);
        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 encryptionService = app.get(encryption_service_1.EncryptionService);
            const sshService = app.get(cli_ssh_service_1.CliSshService);
            const cluster = await observabilityService.getObservabilityCluster();
            if (!cluster) {
                spinner.fail('No observability cluster found');
                return;
            }
            if (cluster.status !== cluster_entity_1.ClusterStatus.READY) {
                spinner.fail(`Cluster not ready (status: ${cluster.status})`);
                return;
            }
            const masterIp = cluster.masterIpAddress;
            if (!masterIp) {
                spinner.fail('Master IP address not available');
                return;
            }
            spinner.succeed('Cluster ready');
            // Stack passwords (Postgres, Redis, Grafana) from DB metadata.
            spinner = (0, ora_1.default)('Resolving stack passwords...').start();
            const passwords = this.resolveStackPasswords(cluster, encryptionService);
            if (!passwords) {
                spinner.fail('Stack passwords not available in cluster metadata');
                return;
            }
            spinner.succeed('Stack passwords resolved');
            // flui-secrets via SSH + kubectl on the master (avoids needing kube-API
            // open or a running tunnel). Falls back gracefully if the secret is missing.
            spinner = (0, ora_1.default)('Reading flui-secrets via SSH...').start();
            const fluiSecrets = await this.readFluiSecrets(sshService, masterIp);
            spinner.succeed('flui-secrets read');
            // OIDC issuer from flui-api-config — used to point local API at the
            // public Zitadel admin URL instead of the in-cluster service DNS.
            spinner = (0, ora_1.default)('Reading OIDC issuer from flui-api-config...').start();
            const oidcIssuer = await this.readOidcIssuer(sshService, masterIp);
            if (oidcIssuer)
                spinner.succeed(`OIDC issuer: ${oidcIssuer}`);
            else
                spinner.warn('OIDC issuer not found in flui-api-config');
            // Local encryption key — shared with API via ~/.flui/encryption.key.
            const encryptionKeyPath = path.join(os.homedir(), '.flui', 'encryption.key');
            const encryptionKey = fs.existsSync(encryptionKeyPath)
                ? fs.readFileSync(encryptionKeyPath, 'utf-8').trim()
                : '';
            const apiPath = await this.resolveApiPath(flags['api-path'], flags.save);
            const apiDir = path.isAbsolute(apiPath)
                ? apiPath
                : path.resolve(process.cwd(), apiPath);
            const envLocalPath = path.join(apiDir, '.env.local');
            const envVars = this.buildEnvVars({
                passwords,
                encryptionKey,
                fluiSecrets,
                oidcIssuer,
            });
            this.printSummary(envLocalPath, envVars);
            if (fluiSecrets.fluiApiKey) {
                // Mirror existing export-config behavior: keep ~/.flui/config.json
                // in sync so that `flui` commands can call the API right away.
                new config_storage_1.ConfigStorage().setApiKey(fluiSecrets.fluiApiKey);
            }
            if (flags['dry-run']) {
                console.log(chalk_1.default.yellow('🔍 Dry run — .env.local not modified\n'));
                return;
            }
            this.writeEnvFile(envLocalPath, apiDir, envVars, !!flags.backup);
            console.log(chalk_1.default.green(`\n✅ Wrote secrets to ${envLocalPath}\n`));
            console.log(chalk_1.default.dim('Next steps:'));
            console.log(`   1. ${chalk_1.default.cyan('flui dev tunnel')}  (in another terminal)`);
            console.log(`   2. ${chalk_1.default.cyan('pnpm start:dev')}\n`);
        }
        catch (error) {
            spinner.fail('Error writing dev credentials');
            console.error(chalk_1.default.red(`\n❌ ${error.message}\n`));
            this.exit(1);
        }
        finally {
            await (0, nest_app_1.closeNestApp)();
        }
    }
    resolveStackPasswords(cluster, encryptionService) {
        const metadata = (cluster.metadata ?? {});
        const stack = metadata.observabilityStack?.passwords;
        if (stack)
            return stack;
        if (metadata.postgresPasswordEncrypted &&
            metadata.redisPasswordEncrypted &&
            metadata.grafanaPasswordEncrypted) {
            try {
                return {
                    postgres: encryptionService.decrypt(metadata.postgresPasswordEncrypted),
                    redis: encryptionService.decrypt(metadata.redisPasswordEncrypted),
                    grafana: encryptionService.decrypt(metadata.grafanaPasswordEncrypted),
                };
            }
            catch {
                return null;
            }
        }
        return null;
    }
    buildEnvVars(opts) {
        const { passwords, encryptionKey, fluiSecrets, oidcIssuer } = opts;
        const envVars = {
            DB_PASSWORD: passwords.postgres,
            REDIS_PASSWORD: passwords.redis,
            GRAFANA_ADMIN_PASSWORD: passwords.grafana,
        };
        if (encryptionKey)
            envVars.ENCRYPTION_KEY = encryptionKey;
        if (fluiSecrets.adminEmail)
            envVars.ADMIN_EMAIL = fluiSecrets.adminEmail;
        if (fluiSecrets.jwtSecret)
            envVars.JWT_SECRET = fluiSecrets.jwtSecret;
        if (fluiSecrets.fluiApiKey)
            envVars.FLUI_API_KEY = fluiSecrets.fluiApiKey;
        if (fluiSecrets.sshCaPrivateKey)
            envVars.SSH_CA_PRIVATE_KEY = fluiSecrets.sshCaPrivateKey;
        if (fluiSecrets.sshCaPublicKey)
            envVars.SSH_CA_PUBLIC_KEY = fluiSecrets.sshCaPublicKey;
        if (fluiSecrets.zitadelPat)
            envVars.ZITADEL_SERVICE_ACCOUNT_PAT = fluiSecrets.zitadelPat;
        if (oidcIssuer)
            envVars.OIDC_PROVIDER_ADMIN_URL = oidcIssuer;
        return envVars;
    }
    writeEnvFile(envLocalPath, apiDir, envVars, backup) {
        let existing = '';
        if (fs.existsSync(envLocalPath)) {
            existing = fs.readFileSync(envLocalPath, 'utf-8');
            if (backup) {
                const stamp = new Date()
                    .toISOString()
                    .replaceAll(':', '-')
                    .replace(/\..+/, '');
                fs.copyFileSync(envLocalPath, `${envLocalPath}.backup.${stamp}`);
            }
        }
        else {
            existing =
                '# Local-only secrets written by `flui dev creds`. Do NOT commit.\n';
        }
        const updated = (0, env_file_1.updateEnvContent)(existing, envVars);
        fs.mkdirSync(apiDir, { recursive: true });
        fs.writeFileSync(envLocalPath, updated, { mode: 0o600 });
        try {
            fs.chmodSync(envLocalPath, 0o600);
        }
        catch {
            /* best-effort */
        }
    }
    async readFluiSecrets(sshService, masterIp) {
        try {
            const raw = await sshService.sshExec(masterIp, "kubectl -n flui-system get secret flui-secrets -o jsonpath='{.data}'");
            const data = JSON.parse(raw);
            const decode = (v) => v ? Buffer.from(v, 'base64').toString('utf-8') : undefined;
            return {
                jwtSecret: decode(data.JWT_SECRET),
                adminEmail: decode(data.ADMIN_EMAIL),
                fluiApiKey: decode(data.FLUI_API_KEY),
                sshCaPrivateKey: decode(data.SSH_CA_PRIVATE_KEY),
                sshCaPublicKey: decode(data.SSH_CA_PUBLIC_KEY),
                zitadelPat: decode(data.ZITADEL_SERVICE_ACCOUNT_PAT),
            };
        }
        catch {
            return {};
        }
    }
    async readOidcIssuer(sshService, masterIp) {
        try {
            const raw = await sshService.sshExec(masterIp, "kubectl -n flui-system get configmap flui-api-config -o jsonpath='{.data.OIDC_ISSUER}'");
            const trimmed = raw.trim();
            return trimmed || undefined;
        }
        catch {
            return undefined;
        }
    }
    async resolveApiPath(explicit, save) {
        const storage = new config_storage_1.ConfigStorage();
        const resolver = new preferences_resolver_1.PreferencesResolver(storage);
        const initial = resolver.resolve('apiPath', explicit);
        if (initial.value !== null)
            return initial.value;
        const def = preferences_schema_1.PREFERENCES.apiPath;
        const value = await (0, prompts_1.promptInput)({
            message: def.description,
            default: def.defaultValue,
            validate: (v) => preferences_resolver_1.PreferencesResolver.validate('apiPath', v),
        });
        if (save)
            storage.setPreference('apiPath', value);
        return value;
    }
    printSummary(envLocalPath, envVars) {
        console.log(chalk_1.default.cyan(`\n📋 Writing to ${envLocalPath}:\n`));
        for (const k of Object.keys(envVars)) {
            console.log(`   ${k}=${chalk_1.default.yellow('******')} ${chalk_1.default.dim('(hidden)')}`);
        }
        console.log();
    }
}
DevCreds.description = 'Developer-only: write cluster secrets (DB/Redis passwords, JWT, encryption key, SSH CA) to <apiPath>/.env.local for local development.\n' +
    '.env.local is gitignored. Run `flui dev tunnel` to expose the matching services on localhost.';
DevCreds.examples = [
    '<%= config.bin %> <%= command.id %>',
    '<%= config.bin %> <%= command.id %> --dry-run',
    '<%= config.bin %> <%= command.id %> --api-path ../flui.api',
];
DevCreds.flags = {
    'dry-run': core_1.Flags.boolean({
        description: 'Print which keys would be written without touching the file',
        default: false,
    }),
    backup: core_1.Flags.boolean({
        description: 'Create a backup of an existing .env.local before overwriting keys',
        default: true,
        allowNo: true,
    }),
    'api-path': core_1.Flags.string({
        description: `Override resolved value for the "apiPath" preference (${preferences_schema_1.PREFERENCES.apiPath.description})`,
    }),
    save: core_1.Flags.boolean({
        description: 'Persist any prompted preference value to the active profile (~/.flui/profiles/<active>/config.json).',
        default: false,
    }),
};
exports.default = DevCreds;
