/**
 * Package Integrity Verification
 * Verifies npm package integrity before updates
 */
import * as crypto from 'crypto';
import * as fs from 'fs';
import { getInstalledGlobalPackageVersion } from '../npm-list-version.js';
import { spawnNpmSync } from '../npm-spawn.js';
import { sanitizePackageName } from './validation.js';
/**
 * Get package integrity hash from npm registry
 */
export function getPackageIntegrity(packageName, version) {
    try {
        // SECURITY: Validate package name
        const validation = sanitizePackageName(packageName);
        if (!validation.valid || !validation.sanitized) {
            throw new Error(`Invalid package name: ${validation.error}`);
        }
        // Get package info from npm (includes integrity hash); spawnNpmSync on Windows
        const result = spawnNpmSync(['view', `${validation.sanitized}@${version}`, 'dist.integrity', '--json']);
        if (result.status !== 0 || !result.stdout) {
            return null;
        }
        const integrity = result.stdout.trim().replace(/"/g, '');
        return integrity || null;
    }
    catch {
        return null;
    }
}
/**
 * Verify package tarball integrity
 */
export function verifyPackageIntegrity(tarballPath, expectedIntegrity) {
    try {
        // Parse integrity string (format: algorithm-base64hash)
        const match = expectedIntegrity.match(/^(sha\d+)-(.+)$/);
        if (!match) {
            return false;
        }
        const [, algorithm, expectedHash] = match;
        // Read tarball and compute hash
        const tarballData = fs.readFileSync(tarballPath);
        const hash = crypto.createHash(algorithm.replace('sha', 'sha')).update(tarballData).digest('base64');
        return hash === expectedHash;
    }
    catch {
        return false;
    }
}
/**
 * Verify installed package version matches expected
 */
export function verifyInstalledVersion(packageName, expectedVersion) {
    try {
        const validation = sanitizePackageName(packageName);
        if (!validation.valid || !validation.sanitized) {
            return false;
        }
        const installedVersion = getInstalledGlobalPackageVersion(packageName);
        return installedVersion === expectedVersion;
    }
    catch {
        return false;
    }
}
/** Build error message from npm spawn result (include both stdout and stderr) */
function npmErrorResult(result) {
    const toStr = (x) => (x == null ? '' : typeof x === 'string' ? x : x.toString('utf-8'));
    const parts = [];
    if (result.error) {
        parts.push(result.error.message);
    }
    const out = [toStr(result.stderr), toStr(result.stdout)].filter(Boolean).join('\n').trim();
    if (out) {
        parts.push(out);
    }
    if (parts.length > 0) {
        return parts.join('\n');
    }
    if (result.status === null) {
        return 'npm did not return an exit code (spawn may have failed — try running npm from an elevated or normal shell, or install Node from nodejs.org so npm-cli.js sits next to node.exe).';
    }
    return `npm exited with code ${result.status}`;
}
/** Resolve a tag (e.g. 'latest') to a concrete version from the registry */
function resolveTagVersion(packageName, tag) {
    try {
        const result = spawnNpmSync(['view', `${packageName}@${tag}`, 'version']);
        if (result.status === 0 && result.stdout) {
            return result.stdout.trim().replace(/"/g, '') || null;
        }
        return null;
    }
    catch {
        return null;
    }
}
/**
 * Safe global npm install (no `--integrity` flag: npm 10+ rejects it as a CLI config; tarball is still verified on fetch).
 * On Windows, npm is invoked via `node .../npm-cli.js` (see npm-spawn.ts) so spawn does not rely on .cmd.
 * When version is 'latest', installs whatever the registry tags as latest (avoids ETARGET for unpublished versions).
 */
export function safeNpmInstall(packageName, version) {
    try {
        const validation = sanitizePackageName(packageName);
        if (!validation.valid || !validation.sanitized) {
            return { success: false, error: `Invalid package name: ${validation.error}` };
        }
        const runInstall = (args) => spawnNpmSync(args);
        // When using 'latest', we verify against the resolved version after install
        const versionToVerify = version === 'latest' ? null : version;
        const result = runInstall([
            'install',
            '-g',
            `${validation.sanitized}@${version}`,
            '--no-audit',
            '--no-fund',
            '--loglevel=error',
            '--progress=false',
        ]);
        if (result.status !== 0) {
            return { success: false, error: npmErrorResult(result) };
        }
        const installed = getInstalledGlobalPackageVersion(validation.sanitized);
        if (version === 'latest') {
            const latestFromRegistry = resolveTagVersion(validation.sanitized, 'latest');
            if (!installed) {
                return {
                    success: false,
                    error: 'Could not read installed version after update (npm list). Run: npm install -g ' +
                        validation.sanitized +
                        '@latest',
                };
            }
            if (latestFromRegistry && installed !== latestFromRegistry) {
                return {
                    success: false,
                    error: `Install finished but global version is still ${installed} (npm registry latest is ${latestFromRegistry}). Close all 4r windows and retry.`,
                };
            }
            return { success: true };
        }
        const verified = (() => {
            const expected = versionToVerify ?? resolveTagVersion(validation.sanitized, version);
            return expected ? verifyInstalledVersion(validation.sanitized, expected) : true;
        })();
        if (!verified) {
            return {
                success: false,
                error: installed == null
                    ? 'Could not read installed version after update (npm list). Run: npm install -g ' +
                        validation.sanitized +
                        '@latest'
                    : 'Installed version does not match expected. Run: npm install -g ' + validation.sanitized + '@latest',
            };
        }
        return { success: true };
    }
    catch (error) {
        return { success: false, error: error?.message || String(error) };
    }
}
//# sourceMappingURL=package-integrity.js.map