"use strict";
/**
 * codex-auth import-default command.
 *
 * Migrates legacy ~/.codex/auth.json into a named profile (non-destructive).
 * Implements C3 torn-write protection: read-with-retry + JWT-shape validation
 * + pgrep Codex-running detection + atomic write.
 *
 * Usage: ccsx auth import-default <name> [--with-history] [--force] [--force-while-running]
 */
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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleImportDefaultCodex = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const childProcess = __importStar(require("child_process"));
const logging_1 = require("../../services/logging");
const ui_1 = require("../../utils/ui");
const ui_2 = require("../../utils/ui");
const errors_1 = require("../../errors");
const exit_codes_1 = require("../../errors/exit-codes");
const index_1 = require("../index");
const decode_id_token_1 = require("../decode-id-token");
const types_1 = require("./types");
const logger = (0, logging_1.createLogger)('codex-auth:cmd:import-default');
// Maximum retries for torn-write detection (C3)
const MAX_READ_RETRIES = 3;
const RETRY_DELAY_MS = 100;
// CLIProxy format marker (reject these with a clear message)
const CLIPROXY_TYPE_MARKER = 'type';
const IMPORT_DEFAULT_USAGE = 'ccsx auth import-default <name> [--with-history] [--force] [--force-while-running]';
// ── helpers ──────────────────────────────────────────────────────────────────
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
 * Detect a running `codex` process via pgrep + ps validation (best-effort, never throws).
 * Returns the PID string if found, null otherwise.
 */
function detectCodexRunning() {
    try {
        const result = childProcess.spawnSync('pgrep', ['-f', 'codex'], {
            encoding: 'utf8',
            timeout: 2000,
        });
        if (result.status !== 0 || !result.stdout || result.stdout.trim().length === 0) {
            return null;
        }
        const pids = parsePgrepPids(result.stdout);
        if (pids.length === 0)
            return null;
        const psResult = childProcess.spawnSync('ps', ['-p', pids.join(','), '-o', 'pid=', '-o', 'command='], {
            encoding: 'utf8',
            timeout: 2000,
        });
        if (psResult.status !== 0 || !psResult.stdout)
            return null;
        return selectCodexPidFromPsOutput(psResult.stdout, pids);
    }
    catch {
        return null;
    }
}
function parsePgrepPids(stdout) {
    return stdout
        .split(/\r?\n/)
        .map((line) => line.trim())
        .filter((pid) => /^\d+$/.test(pid) && pid !== String(process.pid));
}
function selectCodexPidFromPsOutput(stdout, candidatePids) {
    const candidates = new Set(candidatePids);
    for (const line of stdout.split(/\r?\n/)) {
        const match = line.match(/^\s*(\d+)\s+(.+?)\s*$/);
        if (!match)
            continue;
        const [, pid, command] = match;
        if (!pid || !command || !candidates.has(pid))
            continue;
        if (isLikelyCodexProcessCommand(command))
            return pid;
    }
    return null;
}
function isLikelyCodexProcessCommand(command) {
    const executable = firstCommandToken(command);
    if (isCodexExecutableToken(executable)) {
        return true;
    }
    const normalized = command.replace(/\\/g, '/').toLowerCase();
    if (normalized.includes('/@openai/codex/')) {
        return true;
    }
    return command.split(/\s+/).some((token) => {
        const tokenName = executableTokenBasename(token);
        return tokenName === 'codex.js' || isCodexExecutableName(tokenName);
    });
}
function firstCommandToken(command) {
    const trimmed = command.trim();
    const quoted = trimmed.match(/^"([^"]+)"/);
    if (quoted?.[1])
        return quoted[1];
    return trimmed.split(/\s+/)[0] ?? '';
}
function isCodexExecutableToken(token) {
    return isCodexExecutableName(executableTokenBasename(token));
}
function executableTokenBasename(token) {
    return path.basename(token.replace(/^["']|["']$/g, '').replace(/\\/g, '/')).toLowerCase();
}
function isCodexExecutableName(name) {
    return ['codex', 'codex.exe', 'codex.cmd', 'codex.ps1'].includes(name);
}
/**
 * Read and validate auth.json with retry on torn-write (C3).
 * Returns parsed auth JSON or throws on persistent failure.
 */
async function readAuthJsonSafe(authSrcPath) {
    let lastErr = null;
    for (let attempt = 0; attempt < MAX_READ_RETRIES; attempt++) {
        try {
            const buf = fs.readFileSync(authSrcPath, 'utf8');
            const parsed = JSON.parse(buf);
            // Reject cliproxy-format auth files
            if (typeof parsed[CLIPROXY_TYPE_MARKER] === 'string' &&
                (parsed[CLIPROXY_TYPE_MARKER] === 'codex' ||
                    parsed[CLIPROXY_TYPE_MARKER] === 'anthropic' ||
                    parsed[CLIPROXY_TYPE_MARKER] === 'gemini')) {
                throw new Error(`CLIPROXY_FORMAT: Source is a CLIProxy auth file (type="${parsed[CLIPROXY_TYPE_MARKER]}"); use \`ccs cliproxy ...\` instead.`);
            }
            // Validate JWT shape: must have tokens.id_token with a parseable JWT payload.
            const tokens = parsed['tokens'];
            if (tokens) {
                const idToken = tokens['id_token'];
                if (typeof idToken === 'string' && idToken.length > 0) {
                    if (!(0, decode_id_token_1.hasStructurallyValidIdToken)(idToken)) {
                        // Torn write mid-JWT — retry
                        throw new Error('TORN_JWT: id_token payload is not parseable');
                    }
                }
            }
            return parsed;
        }
        catch (err) {
            if (err instanceof Error && err.message.startsWith('CLIPROXY_FORMAT:')) {
                throw err; // never retry CLIProxy format rejection
            }
            lastErr = err instanceof Error ? err : new Error(String(err));
            logger.warn('codex-auth.import-default.torn-read', `Read attempt ${attempt + 1}/${MAX_READ_RETRIES} failed: ${lastErr.message}`);
            if (attempt < MAX_READ_RETRIES - 1) {
                await sleep(RETRY_DELAY_MS);
            }
        }
    }
    throw new Error(`Failed to read a valid auth.json after ${MAX_READ_RETRIES} attempts: ${lastErr?.message ?? 'unknown'}`);
}
/**
 * Atomic copy: write to tmp.<pid>.<rand>, fsync, rename to dest.
 * Preserves 0600 permissions.
 */
function atomicWriteFile(dest, content) {
    const tmpPath = `${dest}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
    try {
        fs.writeFileSync(tmpPath, content, { mode: 0o600 });
        // fsync via close-and-reopen pattern (Bun/Node doesn't expose fd fsync easily)
        const fd = fs.openSync(tmpPath, 'r');
        fs.fsyncSync(fd);
        fs.closeSync(fd);
        fs.renameSync(tmpPath, dest);
    }
    catch (err) {
        try {
            fs.unlinkSync(tmpPath);
        }
        catch {
            // best-effort cleanup
        }
        throw err;
    }
}
/**
 * Copy a file if it exists; silently skip if not.
 * Returns 'copied' | 'missing'.
 */
function copyIfPresent(src, dest) {
    if (!fs.existsSync(src))
        return 'missing';
    const content = fs.readFileSync(src, 'utf8');
    atomicWriteFile(dest, content);
    return 'copied';
}
/**
 * Recursively copy a directory if it exists.
 * Returns count of files copied, or -1 if dir missing.
 */
function copyDirIfPresent(srcDir, destDir) {
    if (!fs.existsSync(srcDir))
        return -1;
    fs.mkdirSync(destDir, { recursive: true, mode: 0o700 });
    let count = 0;
    for (const entry of fs.readdirSync(srcDir)) {
        const srcEntry = path.join(srcDir, entry);
        const destEntry = path.join(destDir, entry);
        const stat = fs.lstatSync(srcEntry);
        if (stat.isDirectory()) {
            count += copyDirIfPresent(srcEntry, destEntry);
        }
        else if (stat.isFile()) {
            fs.copyFileSync(srcEntry, destEntry);
            count++;
        }
    }
    return count;
}
function parseImportDefaultArgs(rawArgs) {
    // --with-history, --force, --force-while-running are distinct flags
    const parsed = (0, types_1.parseArgs)(rawArgs);
    const unknownFlags = parsed.unknownFlags?.filter((flag) => flag !== '--with-history' && flag !== '--force-while-running');
    (0, types_1.rejectUnsupportedOptions)({ ...parsed, unknownFlags }, IMPORT_DEFAULT_USAGE, { force: true });
    const withHistory = rawArgs.includes('--with-history');
    const forceWhileRunning = rawArgs.includes('--force-while-running');
    if (!parsed.profileName)
        return null;
    return {
        name: parsed.profileName,
        withHistory,
        force: parsed.force ?? false,
        forceWhileRunning,
    };
}
async function handleImportDefaultCodex(ctx, rawArgs) {
    await (0, ui_2.initUI)();
    const args = parseImportDefaultArgs(rawArgs);
    if (!args) {
        console.log(`Usage: ${IMPORT_DEFAULT_USAGE}`);
        (0, errors_1.exitWithError)('Profile name required', exit_codes_1.ExitCode.PROFILE_ERROR);
        return;
    }
    const nameError = (0, types_1.getProfileNameError)(args.name);
    if (nameError) {
        (0, errors_1.exitWithError)(nameError, exit_codes_1.ExitCode.PROFILE_ERROR);
        return;
    }
    // Resolve legacy Codex home — LEGACY_CODEX_HOME env allows test hermeticity (D decision)
    const legacyCodexHome = process.env['LEGACY_CODEX_HOME'] ?? path.join(os.homedir(), '.codex');
    const legacyAuthPath = path.join(legacyCodexHome, 'auth.json');
    // Check legacy auth.json exists
    if (!fs.existsSync(legacyAuthPath)) {
        console.log(`  Use \`ccsx auth login ${args.name}\` to authenticate a new profile instead.`);
        (0, errors_1.exitWithError)('No legacy auth.json', exit_codes_1.ExitCode.PROFILE_ERROR);
        return;
    }
    const { registry } = ctx;
    // Profile collision check
    if (registry.hasProfile(args.name) && !args.force) {
        console.log(`  Use --force to overwrite (a .bak-<ts> backup will be created).`);
        (0, errors_1.exitWithError)('Profile already exists', exit_codes_1.ExitCode.PROFILE_ERROR);
        return;
    }
    // Detect Codex running (C3)
    const codexPid = detectCodexRunning();
    if (codexPid && !args.forceWhileRunning) {
        process.stderr.write(`[!] Codex appears to be running (PID ${codexPid}). A token refresh may be in flight.\n`);
        process.stderr.write(`    Quit Codex first, then re-run import-default. Or pass --force-while-running to proceed anyway.\n`);
        (0, errors_1.exitWithError)('Codex is running', exit_codes_1.ExitCode.PROFILE_ERROR);
        return;
    }
    if (codexPid && args.forceWhileRunning) {
        process.stderr.write(`[!] Proceeding with Codex running (--force-while-running). Be aware a refresh may race.\n`);
    }
    // Read + validate source (C3 torn-write protection)
    let authData;
    try {
        authData = await readAuthJsonSafe(legacyAuthPath);
    }
    catch (err) {
        if (err instanceof Error && err.message.startsWith('CLIPROXY_FORMAT:')) {
            (0, errors_1.exitWithError)(err.message, exit_codes_1.ExitCode.PROFILE_ERROR);
            return;
        }
        const msg = err instanceof Error ? err.message : String(err);
        (0, errors_1.exitWithError)(msg, exit_codes_1.ExitCode.GENERAL_ERROR);
        return;
    }
    const profileDir = (0, index_1.resolveCodexProfileDir)(args.name);
    // Create dir
    fs.mkdirSync(profileDir, { recursive: true, mode: 0o700 });
    const destAuthPath = path.join(profileDir, 'auth.json');
    // Backup existing auth.json if --force overwrite
    if (args.force && fs.existsSync(destAuthPath)) {
        const bakPath = `${destAuthPath}.bak-${Date.now()}`;
        fs.copyFileSync(destAuthPath, bakPath);
        process.stderr.write(`[i] Backed up existing auth.json to ${path.basename(bakPath)}\n`);
    }
    // Atomic write (C3)
    atomicWriteFile(destAuthPath, JSON.stringify(authData, null, 2));
    logger.stage('dispatch', 'codex-auth.import-default.copied', 'Copied auth.json to profile', {
        name: args.name,
        dest: destAuthPath,
    });
    // Optional: copy history + sessions (D8 default false)
    let historyStatus;
    let sessionsStatus;
    if (args.withHistory) {
        const legacyHistoryPath = path.join(legacyCodexHome, 'history.jsonl');
        const destHistoryPath = path.join(profileDir, 'history.jsonl');
        const historyCopied = copyIfPresent(legacyHistoryPath, destHistoryPath);
        historyStatus = historyCopied === 'copied' ? 'copied' : 'not present';
        const legacySessionsDir = path.join(legacyCodexHome, 'sessions');
        const destSessionsDir = path.join(profileDir, 'sessions');
        const sessionCount = copyDirIfPresent(legacySessionsDir, destSessionsDir);
        sessionsStatus = sessionCount >= 0 ? `copied ${sessionCount} files` : 'not present';
    }
    else {
        historyStatus = 'not requested';
        sessionsStatus = 'not requested';
    }
    // Ensure shared config symlink (reuse Phase 1 helper)
    try {
        (0, index_1.ensureSharedConfigSymlink)(profileDir);
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        logger.warn('codex-auth.import-default.symlink-failed', 'Symlink creation failed', {
            profileDir,
            error: msg,
        });
        (0, errors_1.exitWithError)(`Failed to prepare profile config.toml: ${msg}`, exit_codes_1.ExitCode.CONFIG_ERROR);
        return;
    }
    // Decode email for display (best-effort)
    const tokens = authData['tokens'];
    const idToken = typeof tokens?.['id_token'] === 'string' ? tokens['id_token'] : null;
    const identity = idToken ? (0, index_1.decodeIdToken)(idToken) : {};
    const emailDisplay = identity.email ?? '(unknown)';
    // Register in registry
    if (registry.hasProfile(args.name)) {
        registry.updateProfile(args.name, {
            last_used: new Date().toISOString(),
            email: identity.email,
            plan_type: identity.plan_type ?? null,
            account_id: identity.account_id,
        });
    }
    else {
        registry.createProfile(args.name, {
            created: new Date().toISOString(),
            last_used: new Date().toISOString(),
            email: identity.email,
            plan_type: identity.plan_type ?? null,
            account_id: identity.account_id,
        });
    }
    // Print summary
    console.log((0, ui_1.ok)(`Imported legacy ${legacyAuthPath} -> profile '${args.name}'`));
    console.log(`  Email   : ${emailDisplay}`);
    console.log(`  History : ${historyStatus}`);
    console.log(`  Sessions: ${sessionsStatus}`);
    console.log(`  Next    : ccsx auth switch ${args.name}`);
}
exports.handleImportDefaultCodex = handleImportDefaultCodex;
//# sourceMappingURL=import-default-command.js.map