// tools/rollback-release.ts — SPEC-737: rollback_release tool handler
import { runRollback } from '../engine/release/rollback-runner.js';
import { writePostmortem } from '../engine/release/postmortem-generator.js';
import { appendTransitionEvent } from '../storage/transition-log.js';
// ---------------------------------------------------------------------------
// Age guard (30 days)
// ---------------------------------------------------------------------------
const MAX_AGE_DAYS = 30;
const MS_PER_DAY = 24 * 60 * 60 * 1000;
/**
 * Parse a semver version string's publish date from `npm view` output.
 * Returns null if the date cannot be determined.
 */
async function getPublishDate(packageName, version) {
    const { spawn } = await import('node:child_process');
    return new Promise((resolve) => {
        const child = spawn('npm', ['view', `${packageName}@${version}`, 'time', '--json'], {
            stdio: ['ignore', 'pipe', 'pipe'],
        });
        let stdout = '';
        child.stdout.on('data', (d) => {
            stdout += d.toString();
        });
        child.on('close', () => {
            try {
                const parsed = JSON.parse(stdout);
                const dateStr = parsed[version];
                if (dateStr) {
                    resolve(new Date(dateStr));
                    return;
                }
            }
            catch {
                // ignore
            }
            resolve(null);
        });
        child.on('error', () => {
            resolve(null);
        });
    });
}
// ---------------------------------------------------------------------------
// Verification smoke test (confirms version is gone after rollback)
// ---------------------------------------------------------------------------
async function runVerificationSmokeTest(version) {
    const { spawn } = await import('node:child_process');
    return new Promise((resolve) => {
        const child = spawn('npx', ['--yes', '@planu/cli@latest', '--version'], {
            stdio: ['ignore', 'pipe', 'pipe'],
        });
        let output = '';
        child.stdout.on('data', (d) => {
            output += d.toString();
        });
        child.stderr.on('data', (d) => {
            output += d.toString();
        });
        const timer = setTimeout(() => {
            child.kill('SIGTERM');
            resolve({ ok: false, output: 'timeout waiting for npx' });
        }, 30_000);
        child.on('close', () => {
            clearTimeout(timer);
            const stillPresent = output.includes(version);
            if (stillPresent) {
                resolve({ ok: false, output });
            }
            else {
                resolve({ ok: true, output });
            }
        });
        child.on('error', (err) => {
            clearTimeout(timer);
            resolve({ ok: false, output: err.message });
        });
    });
}
// ---------------------------------------------------------------------------
// Handler
// ---------------------------------------------------------------------------
export async function handleRollbackRelease(input) {
    const { version, reason, allowAged = false, dryRun = false, projectId = 'default' } = input;
    const packageName = '@planu/cli';
    // ── Age guard ─────────────────────────────────────────────────────────────
    if (!allowAged && !dryRun) {
        const publishDate = await getPublishDate(packageName, version);
        if (publishDate) {
            const ageMs = Date.now() - publishDate.getTime();
            const ageDays = ageMs / MS_PER_DAY;
            if (ageDays > MAX_AGE_DAYS) {
                return {
                    content: [
                        {
                            type: 'text',
                            text: JSON.stringify({
                                error: 'rollback_age_exceeded',
                                code: 422,
                                fixHint: `Versions older than ${MAX_AGE_DAYS} days require manual operator approval. Re-run with allowAged: true.`,
                                ageDays: Math.round(ageDays),
                            }),
                        },
                    ],
                    isError: true,
                };
            }
        }
    }
    // ── Run rollback steps ────────────────────────────────────────────────────
    const rollbackResult = await runRollback({ version, packageName, dryRun });
    // ── Post-mortem skeleton ──────────────────────────────────────────────────
    let postmortemPath;
    if (rollbackResult.ok || dryRun) {
        try {
            postmortemPath = await writePostmortem({
                version,
                reason,
                projectRoot: '.',
            });
        }
        catch {
            // best-effort — don't fail rollback if postmortem write fails
        }
    }
    // ── Verification smoke test ───────────────────────────────────────────────
    let smokeTest;
    if (rollbackResult.ok && !dryRun) {
        smokeTest = await runVerificationSmokeTest(version);
        if (!smokeTest.ok) {
            // Add a fake step to document this
            rollbackResult.steps.push({
                name: 'smoke-test-verify',
                status: 'failed',
                stderr: 'rollback_smoke_failed — version still resolves via npx',
            });
        }
    }
    // ── Transition log entry ──────────────────────────────────────────────────
    let transitionLogEntryId;
    try {
        const entry = await appendTransitionEvent({
            projectId,
            specId: 'release',
            eventType: 'transition',
            reason,
            meta: {
                kind: 'release_rollback',
                version,
                ok: rollbackResult.ok,
                dryRun,
            },
        });
        transitionLogEntryId = entry.id;
    }
    catch {
        // best-effort
    }
    const output = {
        ok: rollbackResult.ok && (!smokeTest || smokeTest.ok),
        steps: rollbackResult.steps.map((s) => ({
            name: s.name,
            status: s.status,
            stderr: s.stderr,
        })),
        postmortemPath,
        smokeTest,
        transitionLogEntryId,
    };
    const additionalHints = [];
    if (!rollbackResult.ok && rollbackResult.failedStep) {
        additionalHints.push(`Failed at step: ${rollbackResult.failedStep}`);
    }
    if (smokeTest && !smokeTest.ok) {
        additionalHints.push('rollback_smoke_failed — npm cache may still serve old tarball. ' +
            'Users can run: npm cache clean --force && npx --ignore-existing @planu/cli@latest');
    }
    return {
        content: [
            {
                type: 'text',
                text: JSON.stringify({ ...output, hints: additionalHints }),
            },
        ],
        isError: !output.ok,
    };
}
//# sourceMappingURL=rollback-release.js.map