/**
 * `agents setup secrets` — guided defaults for `agents secrets` onboarding.
 *
 * The wizard writes setup preferences and delegates imports to the existing
 * `agents secrets import` command, keeping bundle storage as the source of truth.
 */
import chalk from 'chalk';
import * as fs from 'fs';
import * as path from 'path';
import { spawnSync } from 'node:child_process';
import { getCliLaunch } from '../lib/cli-entry.js';
import { getHistoryDir, updateMeta } from '../lib/state.js';
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
import { parsePolicyOpt } from './secrets.js';
function defaultBackendForPlatform(platform = process.platform) {
    return platform === 'darwin' ? 'keychain' : 'file';
}
export function setupSecretsPrefsPath() {
    return path.join(getHistoryDir(), 'setup', 'secrets.json');
}
function parseBackend(raw) {
    const value = (raw ?? defaultBackendForPlatform()).toLowerCase();
    if (value === 'keychain' || value === 'file' || value === 'vault')
        return value;
    throw new Error(`Invalid --backend '${raw}'. Use keychain, file, or vault.`);
}
/** The wizard's `--policy` shares the canonical parser with `agents secrets
 * policy`, so it accepts `hold` (and the `daily`/`session` aliases) rather than
 * carrying its own copy that knows only the retired vocabulary. `parsePolicyOpt`
 * defaults an absent value to `always`; the wizard's own default is `hold`, so
 * it is supplied here rather than inherited. */
export function parsePolicy(raw) {
    return parsePolicyOpt(raw ?? 'hold');
}
function parseImportSource(raw) {
    const value = (raw ?? 'none').toLowerCase();
    if (value === 'none' || value === 'env' || value === '1password' || value === 'icloud')
        return value;
    throw new Error(`Invalid --import-from '${raw}'. Use none, env, 1password, or icloud.`);
}
function hasCommand(command) {
    const res = spawnSync(command, ['--version'], { stdio: 'ignore', windowsHide: true });
    return !res.error;
}
function saveSetupSecretsPrefs(prefs) {
    const file = setupSecretsPrefsPath();
    fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
    fs.writeFileSync(file, JSON.stringify(prefs, null, 2) + '\n', { mode: 0o600 });
    return file;
}
function applySecretsDefaults(backend, policy) {
    updateMeta((meta) => ({
        ...meta,
        secrets: {
            ...meta.secrets,
            backend,
            ...(policy === 'never' ? {} : { policy }),
        },
    }));
}
function backendArgs(backend) {
    return backend === 'vault' ? ['--synced'] : ['--backend', backend];
}
/**
 * Provision the signed macOS keychain broker on the explicit onboarding path.
 * This is the async, download-capable trigger the hot sync `getKeychainHelperPath()`
 * fails loud toward: with a bundled `.app` it is a local copy (no network); on a
 * tarball that lacks the bundle it fetches the verified release asset. Best-effort
 * — the wizard's saved prefs must survive an offline provision failure; the next
 * secret op re-points here.
 */
async function maybeProvisionKeychainHelper(backend) {
    if (backend !== 'keychain' || process.platform !== 'darwin')
        return;
    const { ensureKeychainHelperInstalledAsync } = await import('../lib/secrets/install-helper.js');
    try {
        await ensureKeychainHelperInstalledAsync();
        console.log(chalk.gray('keychain helper: installed the signed broker for this version.'));
    }
    catch (err) {
        console.error(chalk.yellow(`keychain helper not installed: ${err.message}\n` +
            'Re-run `agents setup secrets` once online, or reinstall agents-cli.'));
    }
}
function printBackendNotes(backend) {
    if (backend === 'keychain') {
        console.log(chalk.gray('backend: keychain — macOS reads may ask for Touch ID or the device password.'));
    }
    else if (backend === 'file') {
        console.log(chalk.gray('backend: file — encrypted at rest; headless reads need no passphrase by default, via a machine-local key at ~/.agents/.secrets-key/passphrase.'));
    }
    else {
        console.log(chalk.gray('backend: vault — synced ~/.agents/vault.age storage; unlock it with `agents secrets vault unlock`.'));
    }
}
function runAgentsSubcommand(args) {
    const launch = getCliLaunch(args);
    const res = spawnSync(launch.command, launch.args, { stdio: 'inherit', env: process.env });
    if ((res.status ?? 1) !== 0) {
        throw new Error(`agents ${args.join(' ')} failed with exit ${res.status ?? 1}.`);
    }
}
async function maybeImportSecrets(source, opts) {
    if (source === 'none')
        return;
    let bundle = opts.bundle;
    if (!bundle) {
        if (!isInteractiveTerminal()) {
            throw new Error(`--bundle is required with --import-from ${source} in a non-interactive shell.`);
        }
        const { input } = await import('@inquirer/prompts');
        bundle = await input({
            message: 'Import into which bundle?',
            default: 'default',
            validate: (v) => (v.trim().length > 0 ? true : 'Enter a bundle name.'),
        });
    }
    const args = ['secrets', 'import', bundle, ...backendArgs(opts.backend)];
    if (opts.force)
        args.push('--force');
    if (source === 'env') {
        let envPath = opts.vault;
        if (!envPath) {
            if (!isInteractiveTerminal()) {
                throw new Error('--vault must name the .env path when --import-from env is used non-interactively.');
            }
            const { input } = await import('@inquirer/prompts');
            envPath = await input({
                message: '.env file path',
                default: '.env',
                validate: (v) => (v.trim().length > 0 ? true : 'Enter a .env path.'),
            });
        }
        args.push('--from', envPath);
    }
    else if (source === '1password') {
        const vault = opts.vault;
        if (!vault)
            throw new Error('--vault <name> is required with --import-from 1password.');
        args.push('--from', `1password:${vault}`);
    }
    else {
        args.push('--from', 'icloud');
    }
    runAgentsSubcommand(args);
}
async function resolveInteractiveChoices(opts) {
    if (!isInteractiveTerminal()) {
        return {
            backend: parseBackend(opts.backend),
            policy: parsePolicy(opts.policy),
            importSource: parseImportSource(opts.importFrom),
        };
    }
    const { select } = await import('@inquirer/prompts');
    const backend = opts.backend ? parseBackend(opts.backend) : await select({
        message: 'Default secrets backend',
        default: defaultBackendForPlatform(),
        choices: [
            { name: 'keychain — local OS keychain; reads may ask for Touch ID/device password', value: 'keychain' },
            { name: 'file — passphrase-encrypted file store for headless machines', value: 'file' },
            { name: 'vault — synced ~/.agents/vault.age file; requires `agents secrets vault unlock`', value: 'vault' },
        ],
    });
    const policy = opts.policy ? parsePolicy(opts.policy) : await select({
        message: 'Default prompt policy',
        default: 'hold',
        choices: [
            { name: 'hold — ask once, then hold for the configured window (7 days by default)', value: 'hold' },
            { name: 'always — ask on every read', value: 'always' },
            { name: 'never — no biometry ACL; use only for automation-only bundles', value: 'never' },
        ],
    });
    const importChoices = [
        { name: 'skip import', value: 'none' },
        { name: 'import from .env file', value: 'env' },
        ...(hasCommand('op') ? [{ name: 'import from 1Password vault', value: '1password' }] : []),
        { name: 'import legacy iCloud Keychain bundles', value: 'icloud' },
    ];
    const importSource = opts.importFrom ? parseImportSource(opts.importFrom) : await select({
        message: 'Import existing secrets now?',
        default: 'none',
        choices: importChoices,
    });
    return { backend, policy, importSource };
}
function printOnboardingSummary(backend, policy, prefsFile) {
    console.log(chalk.bold('\nSecrets setup saved.'));
    console.log(chalk.gray(`preferences: ${prefsFile}`));
    printBackendNotes(backend);
    if (policy === 'never') {
        console.log(chalk.yellow('policy: never is saved as a setup preference; create automation-only bundles with --policy never --i-understand.'));
    }
    else {
        console.log(chalk.gray(`policy: ${policy} — used by bundles that do not set their own policy.`));
    }
    console.log(chalk.bold('\nTry:'));
    console.log(chalk.cyan(`  agents secrets create prod ${backendArgs(backend).join(' ')} --policy ${policy === 'never' ? 'hold' : policy}`));
    console.log(chalk.cyan('  agents secrets add prod API_KEY'));
    console.log(chalk.cyan('  agents run claude "deploy" --secrets prod'));
}
export async function runSecretsSetupWizard(opts = {}) {
    const { backend, policy, importSource } = await resolveInteractiveChoices(opts);
    if (policy === 'never' && !opts.iUnderstand && !isInteractiveTerminal()) {
        throw new Error("Refusing to save policy 'never' non-interactively without --i-understand.");
    }
    applySecretsDefaults(backend, policy);
    const prefsFile = saveSetupSecretsPrefs({
        defaultBackend: backend,
        defaultPolicy: policy,
        updatedAt: new Date().toISOString(),
    });
    // Provision the signed keychain broker before any import subprocess reads it.
    await maybeProvisionKeychainHelper(backend);
    await maybeImportSecrets(importSource, {
        bundle: opts.bundle,
        vault: opts.vault,
        backend,
        force: opts.force,
    });
    printOnboardingSummary(backend, policy, prefsFile);
    return true;
}
/** Register `agents setup secrets` under the parent `setup` command. */
export function registerSetupSecretsCommand(setupCmd) {
    setupCmd
        .command('secrets')
        .description('Configure `agents secrets` defaults and optionally import existing secrets.')
        .option('--backend <backend>', 'default backend: keychain, file, or vault')
        .option('--policy <policy>', "default prompt policy: hold, always, or never ('daily'/'session' are accepted aliases for 'hold')")
        .option('--import-from <source>', 'optional import source: none, env, 1password, or icloud')
        .option('--bundle <name>', 'bundle name for optional imports')
        .option('--vault <name-or-path>', '1Password vault name, or .env path with --import-from env')
        .option('--i-understand', 'allow saving the "never" policy preference without an interactive prompt')
        .option('--force', 'pass --force through to optional imports')
        .action(async (options) => {
        try {
            await runSecretsSetupWizard(options);
        }
        catch (err) {
            if (isPromptCancelled(err)) {
                console.log(chalk.yellow('\nCancelled'));
                return;
            }
            console.error(chalk.red(err.message));
            process.exitCode = 1;
        }
    });
}
