/**
 * studiograph audit — read-only operator view of the append-only audit log.
 *
 *   studiograph audit list [filters]
 *     --key <pattern>            Filter by key name (exact match)
 *     --actor <user-id>          Filter by actor user-id
 *     --action <action>          Filter by action type
 *                                (set|delete|test|describe|migrate|...)
 *     --since <iso-date>         Only events at-or-after this time
 *     --until <iso-date>         Only events at-or-before this time
 *     --limit <n>                Page size (default 50, max 500)
 *     --offset <n>               Pagination offset
 *     --format <table|json>      Output format (default table)
 *
 * Audit reads aren't audited themselves (append-only log; reading
 * doesn't change state). The UI used to surface this as a Settings →
 * Audit Log tab; removed because filtering and exporting are much
 * better in a terminal.
 */
import { Command } from 'commander';
import { log } from '@clack/prompts';
import { homedir } from 'os';
import { existsSync } from 'fs';
import { join } from 'path';
import { SqliteEncryptedStore, loadMasterKey, loadPreviousMasterKey, setSecretStore, } from '../../../core/secrets/index.js';
function openStore() {
    const loaded = loadMasterKey();
    const previous = loadPreviousMasterKey();
    const sgRoot = process.env.RAILWAY_ENVIRONMENT && existsSync('/workspace')
        ? '/workspace'
        : join(homedir(), '.studiograph');
    const dbPath = join(sgRoot, 'secrets.db');
    if (!existsSync(dbPath)) {
        log.error(`Secrets store not found at ${dbPath}`);
        log.info('Run `studiograph init` first, or start `studiograph serve` to initialize the store.');
        process.exit(1);
    }
    const store = new SqliteEncryptedStore({
        dbPath,
        masterKey: loaded.key,
        previousMasterKey: previous,
    });
    setSecretStore(store);
    return store;
}
function parseTs(v) {
    if (!v)
        return undefined;
    const n = Number(v);
    if (Number.isFinite(n) && n > 0)
        return n;
    const d = Date.parse(v);
    return Number.isFinite(d) ? d : undefined;
}
function fmtTs(ms) {
    return new Date(ms).toISOString();
}
function fmtActor(row) {
    return row.actor_type === 'user'
        ? `user:${row.actor_user_id ?? '?'}`
        : `system:${row.actor_system_id ?? '?'}`;
}
function fmtTable(rows) {
    if (rows.length === 0)
        return '';
    const cols = Object.keys(rows[0]);
    const widths = cols.map((c) => Math.max(c.length, ...rows.map((r) => r[c]?.length ?? 0)));
    const header = cols.map((c, i) => c.padEnd(widths[i])).join('  ');
    const sep = widths.map((w) => '─'.repeat(w)).join('  ');
    const body = rows.map((r) => cols.map((c, i) => (r[c] ?? '').padEnd(widths[i])).join('  ')).join('\n');
    return `${header}\n${sep}\n${body}`;
}
export const auditCommand = new Command('audit')
    .description('Read the append-only audit log (formerly the Settings → Audit Log tab)');
auditCommand
    .command('list')
    .description('List recent audit events, newest first. Filters compose with AND.')
    .option('--key <key>', 'Filter by key name (exact match)')
    .option('--actor <user-id>', 'Filter by actor user-id')
    .option('--actor-type <type>', 'Filter by actor type: user or system')
    .option('--action <action>', 'Filter by action (set, delete, test, describe, migrate, decrypt_fail, rotate_master, bundle_export, bundle_import)')
    .option('--since <iso-date>', 'Only events at-or-after this time (ISO date or epoch ms)')
    .option('--until <iso-date>', 'Only events at-or-before this time (ISO date or epoch ms)')
    .option('--limit <n>', 'Page size, default 50, max 500', '50')
    .option('--offset <n>', 'Pagination offset', '0')
    .option('--format <format>', 'Output format: table (default) or json', 'table')
    .action(async (opts) => {
    const store = openStore();
    try {
        const where = [];
        const params = [];
        if (opts.key) {
            where.push('key = ?');
            params.push(opts.key);
        }
        if (opts.actor) {
            where.push('actor_user_id = ?');
            params.push(opts.actor);
        }
        if (opts.actorType) {
            where.push('actor_type = ?');
            params.push(opts.actorType);
        }
        if (opts.action) {
            where.push('action = ?');
            params.push(opts.action);
        }
        const since = parseTs(opts.since);
        const until = parseTs(opts.until);
        if (since !== undefined) {
            where.push('ts >= ?');
            params.push(since);
        }
        if (until !== undefined) {
            where.push('ts <= ?');
            params.push(until);
        }
        const limit = Math.min(Math.max(parseInt(opts.limit, 10) || 50, 1), 500);
        const offset = Math.max(parseInt(opts.offset, 10) || 0, 0);
        const sql = `SELECT id, ts, actor_type, actor_user_id, actor_system_id, action,
                key, success, ip, user_agent, detail_json
           FROM audit_log
          ${where.length ? `WHERE ${where.join(' AND ')}` : ''}
          ORDER BY ts DESC
          LIMIT ? OFFSET ?`;
        // `store.db` is private; the route code already reaches in via `(store as any).db`
        // and we follow the same pattern. Refactoring the store to expose a public
        // query helper is out of scope for this CLI.
        const rows = (store.db
            .prepare(sql)
            .all(...params, limit, offset));
        const totalRow = (store.db
            .prepare(`SELECT COUNT(*) AS n FROM audit_log ${where.length ? `WHERE ${where.join(' AND ')}` : ''}`)
            .get(...params));
        if (opts.format === 'json') {
            console.log(JSON.stringify({
                total: totalRow.n,
                limit,
                offset,
                items: rows.map((r) => ({
                    id: r.id,
                    ts: r.ts,
                    actor_type: r.actor_type,
                    actor_user_id: r.actor_user_id,
                    actor_system_id: r.actor_system_id,
                    action: r.action,
                    key: r.key,
                    success: !!r.success,
                    ip: r.ip,
                    user_agent: r.user_agent,
                    detail: r.detail_json ? JSON.parse(r.detail_json) : null,
                })),
            }, null, 2));
            return;
        }
        if (rows.length === 0) {
            log.info(`No audit events found${where.length ? ' for the given filters' : ''}.`);
            return;
        }
        const tableRows = rows.map((r) => ({
            time: fmtTs(r.ts),
            actor: fmtActor(r),
            action: r.action,
            key: r.key ?? '—',
            result: r.success ? 'ok' : 'fail',
        }));
        console.log(fmtTable(tableRows));
        console.log(`\nShowing ${rows.length} of ${totalRow.n} events (offset=${offset}, limit=${limit})`);
    }
    finally {
        store.close();
    }
});
//# sourceMappingURL=audit.js.map