// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * @file onboarding-smoke-test.ts
 * Onboarding smoke-test gate for lumenflow init + wu:create flows (WU-1315)
 *
 * This gate creates a temp repo, runs lumenflow init --full, validates:
 * - Injected package.json scripts use standalone binary format
 * - workspace.yaml remains the only lane artifact after init
 * - wu:create works with requireRemote=false
 *
 * Used as part of the gates pipeline to catch regressions before release.
 */
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import * as yaml from 'yaml';
import { execFileSync } from 'node:child_process';
import { WORKSPACE_CONFIG_FILE_NAME } from '@lumenflow/core/config';
import { WORKSPACE_V2_KEYS } from '@lumenflow/core/config-schema';
import { createWuPaths } from '@lumenflow/core/wu-paths';
import { scaffoldProject } from './init.js';
/** Package.json file name constant */
const PACKAGE_JSON_FILE = 'package.json';
/** Canonical workspace config file name */
const WORKSPACE_CONFIG_FILE = WORKSPACE_CONFIG_FILE_NAME;
/** Canonical workspace software delivery section key */
const SOFTWARE_DELIVERY_KEY = WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY;
/** Git binary path - uses system PATH which is acceptable for smoke tests */
const GIT_BINARY = 'git';
/** Required package.json scripts from LumenFlow init */
const REQUIRED_SCRIPTS = ['wu:claim', 'wu:done', 'wu:create', 'gates'];
function asRecord(value) {
    return value && typeof value === 'object' && !Array.isArray(value)
        ? value
        : null;
}
/**
 * Validate that package.json has the required LumenFlow scripts
 * in the correct standalone binary format.
 *
 * @param options - Validation options
 * @returns Validation result
 */
export function validateInitScripts(options) {
    const { projectDir } = options;
    const packageJsonPath = path.join(projectDir, PACKAGE_JSON_FILE);
    // Check if package.json exists
    if (!fs.existsSync(packageJsonPath)) {
        return {
            valid: false,
            missingScripts: [],
            invalidScripts: [],
            error: `${PACKAGE_JSON_FILE} not found in ${projectDir}`,
        };
    }
    let packageJson;
    try {
        packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
    }
    catch (err) {
        return {
            valid: false,
            missingScripts: [],
            invalidScripts: [],
            error: `Failed to parse ${PACKAGE_JSON_FILE}: ${err instanceof Error ? err.message : String(err)}`,
        };
    }
    const scripts = packageJson.scripts ?? {};
    const missingScripts = [];
    const invalidScripts = [];
    for (const script of REQUIRED_SCRIPTS) {
        if (!scripts[script]) {
            missingScripts.push(script);
        }
        else {
            // Validate script uses standalone binary format
            // Should be 'wu-claim' or 'gates', not 'pnpm exec lumenflow wu:claim'
            const value = scripts[script];
            if (value.includes('pnpm exec') || value.includes('npx lumenflow')) {
                invalidScripts.push(script);
            }
        }
    }
    return {
        valid: missingScripts.length === 0 && invalidScripts.length === 0,
        missingScripts,
        invalidScripts,
    };
}
/**
 * Validate that init scaffolds workspace.yaml without recreating the deleted
 * lane-inference sidecar artifact.
 *
 * @param options - Validation options
 * @returns Validation result
 */
export function validateWorkspaceLaneScaffold(options) {
    const { projectDir } = options;
    const configPath = path.join(projectDir, WORKSPACE_CONFIG_FILE);
    if (!fs.existsSync(configPath)) {
        return {
            valid: false,
            errors: [],
            error: `${WORKSPACE_CONFIG_FILE} not found in ${projectDir}`,
        };
    }
    const legacyLaneInferencePath = path.join(projectDir, '.lumenflow.lane-inference.yaml');
    if (fs.existsSync(legacyLaneInferencePath)) {
        return {
            valid: false,
            errors: [],
            error: `.lumenflow.lane-inference.yaml should not be scaffolded in ${projectDir}`,
        };
    }
    let content;
    try {
        const rawContent = fs.readFileSync(configPath, 'utf-8');
        content = yaml.parse(rawContent) ?? {};
    }
    catch (err) {
        return {
            valid: false,
            errors: [],
            error: `Failed to parse ${WORKSPACE_CONFIG_FILE}: ${err instanceof Error ? err.message : String(err)}`,
        };
    }
    const errors = [];
    const lifecycleStatus = content?.software_delivery?.lanes?.lifecycle?.status;
    if (lifecycleStatus !== 'unconfigured') {
        errors.push(`Expected ${WORKSPACE_CONFIG_FILE} lane lifecycle to remain "unconfigured" after init.`);
    }
    return {
        valid: errors.length === 0,
        errors,
    };
}
/**
 * Initialize a git repository in the given directory
 */
// git binary is resolved from PATH; this is a CLI smoke test that requires git
/* eslint-disable sonarjs/no-os-command-from-path */
function initializeGitRepo(projectDir) {
    execFileSync(GIT_BINARY, ['init'], { cwd: projectDir, stdio: 'pipe' });
    execFileSync(GIT_BINARY, ['config', 'user.email', 'test@example.com'], {
        cwd: projectDir,
        stdio: 'pipe',
    });
    execFileSync(GIT_BINARY, ['config', 'user.name', 'Test User'], {
        cwd: projectDir,
        stdio: 'pipe',
    });
    // Create initial commit
    execFileSync(GIT_BINARY, ['add', '-A'], { cwd: projectDir, stdio: 'pipe' });
    execFileSync(GIT_BINARY, ['commit', '-m', 'Initial commit', '--allow-empty'], {
        cwd: projectDir,
        stdio: 'pipe',
    });
}
/* eslint-enable sonarjs/no-os-command-from-path */
/**
 * Create a sample WU YAML file for testing
 */
function createSampleWuYaml(projectDir) {
    const wuDir = path.join(projectDir, createWuPaths({ projectRoot: projectDir }).WU_DIR());
    fs.mkdirSync(wuDir, { recursive: true });
    const wuYaml = `id: WU-TEST-001
title: Test WU
lane: 'Framework: Core'
type: feature
status: ready
priority: P3
created: 2026-02-02
code_paths:
  - 'src/**'
acceptance:
  - Test passes
`;
    fs.writeFileSync(path.join(wuDir, 'WU-TEST-001.yaml'), wuYaml);
}
/**
 * Validate that wu:create works with requireRemote=false config.
 *
 * This creates a minimal WU in the test project to verify the flow works
 * without a git remote.
 *
 * @param options - Validation options
 * @returns Validation result
 */
async function validateWuCreate(options) {
    const { projectDir } = options;
    // Update workspace.yaml software_delivery.git.requireRemote=false
    const configPath = path.join(projectDir, WORKSPACE_CONFIG_FILE);
    const existingWorkspace = fs.existsSync(configPath)
        ? asRecord(yaml.parse(fs.readFileSync(configPath, 'utf-8')))
        : null;
    const workspace = existingWorkspace ?? {};
    const softwareDelivery = asRecord(workspace[SOFTWARE_DELIVERY_KEY]) ?? {};
    const gitConfig = asRecord(softwareDelivery.git) ?? {};
    gitConfig.requireRemote = false;
    softwareDelivery.git = gitConfig;
    workspace[SOFTWARE_DELIVERY_KEY] = softwareDelivery;
    fs.writeFileSync(configPath, yaml.stringify(workspace));
    try {
        initializeGitRepo(projectDir);
    }
    catch (err) {
        return {
            success: false,
            error: `Failed to initialize git repo: ${err instanceof Error ? err.message : String(err)}`,
        };
    }
    // Create a sample WU YAML to simulate wu:create output
    // Note: We don't actually run wu:create as it requires the full CLI to be installed
    // Instead we validate the config would allow it to work
    createSampleWuYaml(projectDir);
    return { success: true };
}
/**
 * Collect errors from validation results
 */
function collectScriptsErrors(scriptsResult) {
    const errors = [];
    if (scriptsResult.error) {
        errors.push(`Init scripts validation error: ${scriptsResult.error}`);
    }
    if (scriptsResult.missingScripts.length > 0) {
        errors.push(`Missing scripts: ${scriptsResult.missingScripts.join(', ')}`);
    }
    if (scriptsResult.invalidScripts.length > 0) {
        errors.push(`Invalid script format: ${scriptsResult.invalidScripts.join(', ')}`);
    }
    return errors;
}
/**
 * Collect errors from lane validation results
 */
function collectLaneErrors(laneResult) {
    const errors = [];
    if (laneResult.error) {
        errors.push(`Workspace lane validation error: ${laneResult.error}`);
    }
    errors.push(...laneResult.errors);
    return errors;
}
/**
 * Run validations in the temp directory
 */
async function runValidations(tempDir, skipWuCreate) {
    const errors = [];
    // Step 1: Run lumenflow init --full
    await scaffoldProject(tempDir, { force: true, full: true });
    // Step 2: Validate init scripts
    const scriptsResult = validateInitScripts({ projectDir: tempDir });
    if (!scriptsResult.valid) {
        errors.push(...collectScriptsErrors(scriptsResult));
    }
    // Step 3: Validate workspace-only lane scaffold
    const laneResult = validateWorkspaceLaneScaffold({ projectDir: tempDir });
    if (!laneResult.valid) {
        errors.push(...collectLaneErrors(laneResult));
    }
    // Step 4: Validate wu:create with requireRemote=false (if not skipped)
    let wuResult;
    if (!skipWuCreate) {
        wuResult = await validateWuCreate({ projectDir: tempDir });
        if (!wuResult.success && wuResult.error) {
            errors.push(`wu:create validation error: ${wuResult.error}`);
        }
    }
    return { scriptsResult, laneResult, wuResult, errors };
}
/**
 * Clean up temporary directory
 */
function cleanupTempDir(tempDir) {
    if (tempDir && fs.existsSync(tempDir)) {
        try {
            fs.rmSync(tempDir, { recursive: true, force: true });
        }
        catch {
            // Ignore cleanup errors
        }
    }
}
/**
 * Run the full onboarding smoke test.
 *
 * Creates a temp directory, runs lumenflow init --full, and validates:
 * 1. Package.json scripts are correctly injected
 * 2. workspace-only lane scaffold is preserved
 * 3. wu:create would work with requireRemote=false
 *
 * @param options - Smoke test options
 * @returns Smoke test result
 */
export async function runOnboardingSmokeTest(options = {}) {
    const { cleanup = true, skipWuCreate = false } = options;
    let { tempDir } = options;
    const result = {
        success: false,
        errors: [],
    };
    // Create temp directory if not provided
    if (!tempDir) {
        tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-smoke-test-'));
        result.tempDir = tempDir;
    }
    // Ensure directory exists
    if (!fs.existsSync(tempDir)) {
        try {
            fs.mkdirSync(tempDir, { recursive: true });
        }
        catch (err) {
            return {
                success: false,
                errors: [
                    `Failed to create temp directory: ${err instanceof Error ? err.message : String(err)}`,
                ],
            };
        }
    }
    try {
        const { scriptsResult, laneResult, wuResult, errors } = await runValidations(tempDir, skipWuCreate);
        result.initScriptsValidation = scriptsResult;
        result.workspaceLaneValidation = laneResult;
        result.wuCreateValidation = wuResult;
        result.errors = errors;
        result.success = errors.length === 0;
    }
    catch (err) {
        result.errors = [`Smoke test failed: ${err instanceof Error ? err.message : String(err)}`];
        result.success = false;
    }
    finally {
        if (cleanup) {
            cleanupTempDir(tempDir);
        }
    }
    return result;
}
/**
 * Run the onboarding smoke test as a gate.
 *
 * This is the entry point for the gates pipeline.
 *
 * @param options - Gate options
 * @returns Gate result with ok status and duration
 */
export async function runOnboardingSmokeTestGate(options) {
    const start = Date.now();
    const logger = options.logger ?? console;
    logger.log('Running onboarding smoke test...');
    const result = await runOnboardingSmokeTest({ cleanup: true });
    if (result.success) {
        logger.log('Onboarding smoke test passed.');
    }
    else {
        logger.log('Onboarding smoke test failed:');
        for (const error of result.errors) {
            logger.log(`  - ${error}`);
        }
    }
    return {
        ok: result.success,
        duration: Date.now() - start,
    };
}
//# sourceMappingURL=onboarding-smoke-test.js.map