"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestExecutor = void 0;
const express_1 = __importDefault(require("express"));
const cors_1 = __importDefault(require("cors"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
// Read version from package.json
function getVersion() {
    try {
        // Method 1: Try require.resolve (works for global/local installs)
        try {
            const pkgPath = require.resolve('@testsmith/testblocks/package.json');
            const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
            if (pkg.version)
                return pkg.version;
        }
        catch {
            // Package might not be installed under this name
        }
        // Method 2: Try relative paths
        const possiblePaths = [
            path_1.default.join(__dirname, '../../package.json'),
            path_1.default.join(__dirname, '../../../package.json'),
            path_1.default.join(__dirname, '../../../../package.json'),
        ];
        for (const pkgPath of possiblePaths) {
            if (fs_1.default.existsSync(pkgPath)) {
                const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
                if (pkg.name === '@testsmith/testblocks' && pkg.version) {
                    return pkg.version;
                }
            }
        }
    }
    catch {
        // Ignore errors
    }
    return '0.0.0';
}
const VERSION = getVersion();
const executor_1 = require("./executor");
Object.defineProperty(exports, "TestExecutor", { enumerable: true, get: function () { return executor_1.TestExecutor; } });
const reporters_1 = require("../cli/reporters");
const plugins_1 = require("./plugins");
const globals_1 = require("./globals");
const codegenManager_1 = require("./codegenManager");
// Set plugins directory (default to examples/plugins or can be overridden via env)
const pluginsDir = process.env.PLUGINS_DIR || path_1.default.join(process.cwd(), 'examples', 'plugins');
(0, plugins_1.setPluginsDirectory)(pluginsDir);
// Set globals directory (default to examples or can be overridden via env)
const globalsDir = process.env.GLOBALS_DIR || path_1.default.join(process.cwd(), 'examples');
(0, globals_1.setGlobalsDirectory)(globalsDir);
// Load all discovered plugins on startup
(0, plugins_1.loadAllPlugins)().then(() => {
    (0, plugins_1.initializeServerPlugins)();
    // Load globals and snippets after plugins
    (0, globals_1.initializeGlobalsAndSnippets)();
}).catch(err => {
    console.error('Failed to load plugins:', err);
});
/**
 * Merge folder hooks into a test file.
 * Folder hooks are ordered from outermost to innermost folder.
 * - beforeAll: run parent hooks first, then child hooks, then test file hooks
 * - afterAll: run test file hooks first, then child hooks, then parent hooks
 * - beforeEach/afterEach: same pattern
 */
function mergeFolderHooksIntoTestFile(testFile, folderHooks) {
    if (!folderHooks || folderHooks.length === 0) {
        return testFile;
    }
    // Collect all steps from folder hooks (parent to child order is already provided)
    const beforeAllSteps = [];
    const afterAllSteps = [];
    const beforeEachSteps = [];
    const afterEachSteps = [];
    // Parent to child order for beforeAll/beforeEach
    for (const hooks of folderHooks) {
        if (hooks.beforeAll)
            beforeAllSteps.push(...hooks.beforeAll);
        if (hooks.beforeEach)
            beforeEachSteps.push(...hooks.beforeEach);
    }
    // Child to parent order for afterAll/afterEach
    for (let i = folderHooks.length - 1; i >= 0; i--) {
        const hooks = folderHooks[i];
        if (hooks.afterAll)
            afterAllSteps.unshift(...hooks.afterAll);
        if (hooks.afterEach)
            afterEachSteps.unshift(...hooks.afterEach);
    }
    // Merge with test file hooks
    const merged = {
        ...testFile,
        beforeAll: [
            ...beforeAllSteps,
            ...(testFile.beforeAll || []),
        ],
        afterAll: [
            ...(testFile.afterAll || []),
            ...afterAllSteps,
        ],
        beforeEach: [
            ...beforeEachSteps,
            ...(testFile.beforeEach || []),
        ],
        afterEach: [
            ...(testFile.afterEach || []),
            ...afterEachSteps,
        ],
    };
    return merged;
}
const app = (0, express_1.default)();
const PORT = process.env.PORT || 3001;
app.use((0, cors_1.default)());
app.use(express_1.default.json({ limit: '10mb' }));
// Health check
app.get('/api/health', (req, res) => {
    res.json({ status: 'ok', version: VERSION });
});
// Version endpoint
app.get('/api/version', (req, res) => {
    res.json({ version: VERSION });
});
// List available plugins (with full block definitions for client registration)
app.get('/api/plugins', (req, res) => {
    const available = (0, plugins_1.discoverPlugins)();
    const loaded = (0, plugins_1.getServerPlugins)().map(p => ({
        name: p.name,
        version: p.version,
        description: p.description,
        blocks: p.blocks, // Include full block definitions
    }));
    res.json({
        directory: (0, plugins_1.getPluginsDirectory)(),
        available,
        loaded,
    });
});
// Load specific plugins
app.post('/api/plugins/load', async (req, res) => {
    try {
        const { plugins } = req.body;
        await (0, plugins_1.loadTestFilePlugins)(plugins);
        res.json({ loaded: plugins });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
// Get globals and snippets
app.get('/api/globals', (req, res) => {
    const globals = (0, globals_1.getGlobals)();
    const snippets = (0, globals_1.getAllSnippets)().map(s => ({
        name: s.name,
        description: s.description,
        category: s.category,
        params: s.params,
        stepCount: s.steps.length,
    }));
    res.json({
        directory: (0, globals_1.getGlobalsDirectory)(),
        globals,
        snippets,
        testIdAttribute: (0, globals_1.getTestIdAttribute)(),
    });
});
// Update test ID attribute
app.put('/api/globals/test-id-attribute', (req, res) => {
    const { testIdAttribute } = req.body;
    if (!testIdAttribute || typeof testIdAttribute !== 'string') {
        return res.status(400).json({ error: 'testIdAttribute is required and must be a string' });
    }
    (0, globals_1.setTestIdAttribute)(testIdAttribute);
    res.json({ testIdAttribute: (0, globals_1.getTestIdAttribute)() });
});
// Run tests
app.post('/api/run', async (req, res) => {
    try {
        const { testFile, folderHooks } = req.body;
        if (!testFile || !testFile.tests) {
            return res.status(400).json({ error: 'Invalid test file format' });
        }
        console.log(`Running ${testFile.tests.length} tests from "${testFile.name}"...`);
        // Merge folder hooks into test file (parent to child order for beforeAll, child to parent for afterAll)
        const mergedTestFile = mergeFolderHooksIntoTestFile(testFile, folderHooks || []);
        // Merge global variables with test file variables
        const globalVars = (0, globals_1.getGlobalVariables)();
        const testIdAttr = (0, globals_1.getTestIdAttribute)();
        const executor = new executor_1.TestExecutor({
            headless: req.query.headless !== 'false',
            timeout: Number(req.query.timeout) || 30000,
            variables: globalVars, // Pass global variables
            testIdAttribute: testIdAttr, // Pass test ID attribute
            baseDir: globalsDir, // Base directory for resolving relative file paths
        });
        const results = await executor.runTestFile(mergedTestFile);
        const passed = results.filter(r => r.status === 'passed').length;
        const failed = results.filter(r => r.status === 'failed').length;
        console.log(`Results: ${passed} passed, ${failed} failed`);
        res.json(results);
    }
    catch (error) {
        const err = error;
        console.error('Test execution failed:', err.message);
        console.error('Stack:', err.stack);
        res.status(500).json({
            error: 'Test execution failed',
            message: err.message,
        });
    }
});
// Run a single test
app.post('/api/run/:testId', async (req, res) => {
    try {
        const { testFile, folderHooks } = req.body;
        const { testId } = req.params;
        const test = testFile.tests.find(t => t.id === testId);
        if (!test) {
            return res.status(404).json({ error: `Test not found: ${testId}` });
        }
        // For single test runs, merge folder beforeEach/afterEach hooks
        // (beforeAll/afterAll are handled at suite level)
        const mergedTestFile = mergeFolderHooksIntoTestFile(testFile, folderHooks || []);
        // Merge global variables
        const globalVars = (0, globals_1.getGlobalVariables)();
        const testIdAttr = (0, globals_1.getTestIdAttribute)();
        const executor = new executor_1.TestExecutor({
            headless: req.query.headless !== 'false',
            timeout: Number(req.query.timeout) || 30000,
            variables: globalVars,
            testIdAttribute: testIdAttr,
            baseDir: globalsDir, // Base directory for resolving relative file paths
        });
        // Register custom blocks from procedures before running the test
        if (mergedTestFile.procedures) {
            executor.registerProcedures(mergedTestFile.procedures);
        }
        await executor.initialize();
        const result = await executor.runTest(test, mergedTestFile.variables);
        await executor.cleanup();
        res.json(result);
    }
    catch (error) {
        console.error('Test execution failed:', error);
        res.status(500).json({
            error: 'Test execution failed',
            message: error.message,
        });
    }
});
// Validate test file
app.post('/api/validate', (req, res) => {
    try {
        const testFile = req.body;
        const errors = [];
        if (!testFile.version) {
            errors.push('Missing version field');
        }
        if (!testFile.name) {
            errors.push('Missing name field');
        }
        if (!testFile.tests || !Array.isArray(testFile.tests)) {
            errors.push('Missing or invalid tests array');
        }
        else {
            testFile.tests.forEach((test, index) => {
                if (!test.id) {
                    errors.push(`Test at index ${index} is missing an id`);
                }
                if (!test.name) {
                    errors.push(`Test at index ${index} is missing a name`);
                }
            });
        }
        if (errors.length > 0) {
            res.status(400).json({ valid: false, errors });
        }
        else {
            res.json({ valid: true });
        }
    }
    catch (error) {
        res.status(400).json({
            valid: false,
            errors: ['Invalid JSON: ' + error.message],
        });
    }
});
// ===== Recording Endpoints =====
// Start a recording session
app.post('/api/record/start', async (req, res) => {
    try {
        const { url, testIdAttribute } = req.body;
        if (!url) {
            return res.status(400).json({ error: 'URL is required' });
        }
        console.log(`Starting recording session for URL: ${url}`);
        const sessionId = await codegenManager_1.codegenManager.startRecording(url, {
            testIdAttribute: testIdAttribute || undefined,
        });
        res.json({
            sessionId,
            status: 'running',
        });
    }
    catch (error) {
        console.error('Failed to start recording:', error);
        res.status(500).json({
            error: 'Failed to start recording',
            message: error.message,
        });
    }
});
// Stop a recording session and get the recorded steps
app.post('/api/record/stop', async (req, res) => {
    try {
        const { sessionId } = req.body;
        if (!sessionId) {
            return res.status(400).json({ error: 'Session ID is required' });
        }
        console.log(`Stopping recording session: ${sessionId}`);
        const steps = await codegenManager_1.codegenManager.stopRecording(sessionId);
        res.json({
            status: 'completed',
            steps,
        });
        // Clean up the session after returning the steps
        setTimeout(() => {
            codegenManager_1.codegenManager.cleanup(sessionId);
        }, 5000);
    }
    catch (error) {
        console.error('Failed to stop recording:', error);
        res.status(500).json({
            error: 'Failed to stop recording',
            message: error.message,
        });
    }
});
// Get the status of a recording session
app.get('/api/record/status/:sessionId', (req, res) => {
    const { sessionId } = req.params;
    const session = codegenManager_1.codegenManager.getStatus(sessionId);
    if (!session) {
        return res.status(404).json({ error: 'Session not found' });
    }
    res.json({
        sessionId: session.id,
        status: session.status,
        url: session.url,
        error: session.error,
    });
});
// ===== Report Generation Endpoints =====
// Generate HTML report from test results
app.post('/api/reports/html', (req, res) => {
    try {
        const { testFile, results } = req.body;
        if (!testFile || !results) {
            return res.status(400).json({ error: 'testFile and results are required' });
        }
        const timestamp = new Date().toISOString();
        const reportData = {
            timestamp,
            summary: {
                totalTests: results.length,
                passed: results.filter(r => r.status === 'passed').length,
                failed: results.filter(r => r.status !== 'passed').length,
                duration: results.reduce((sum, r) => sum + r.duration, 0),
            },
            testFiles: [{
                    file: testFile.name || 'TestBlocks Test',
                    testFile,
                    results,
                }],
        };
        const html = (0, reporters_1.generateHTMLReport)(reportData);
        const filename = `report-${(0, reporters_1.getTimestamp)()}.html`;
        res.setHeader('Content-Type', 'text/html');
        res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
        res.send(html);
    }
    catch (error) {
        console.error('Failed to generate HTML report:', error);
        res.status(500).json({
            error: 'Failed to generate report',
            message: error.message,
        });
    }
});
// Generate JUnit XML report from test results
app.post('/api/reports/junit', (req, res) => {
    try {
        const { testFile, results } = req.body;
        if (!testFile || !results) {
            return res.status(400).json({ error: 'testFile and results are required' });
        }
        const timestamp = new Date().toISOString();
        const reportData = {
            timestamp,
            summary: {
                totalTests: results.length,
                passed: results.filter(r => r.status === 'passed').length,
                failed: results.filter(r => r.status !== 'passed').length,
                duration: results.reduce((sum, r) => sum + r.duration, 0),
            },
            testFiles: [{
                    file: testFile.name || 'TestBlocks Test',
                    testFile,
                    results,
                }],
        };
        const xml = (0, reporters_1.generateJUnitXML)(reportData);
        const filename = `junit-${(0, reporters_1.getTimestamp)()}.xml`;
        res.setHeader('Content-Type', 'application/xml');
        res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
        res.send(xml);
    }
    catch (error) {
        console.error('Failed to generate JUnit report:', error);
        res.status(500).json({
            error: 'Failed to generate report',
            message: error.message,
        });
    }
});
// Clean up sessions on server shutdown
process.on('SIGTERM', () => {
    console.log('Received SIGTERM, cleaning up...');
    codegenManager_1.codegenManager.cleanupAll();
    process.exit(0);
});
process.on('SIGINT', () => {
    console.log('Received SIGINT, cleaning up...');
    codegenManager_1.codegenManager.cleanupAll();
    process.exit(0);
});
app.listen(PORT, () => {
    console.log(`TestBlocks server running on http://localhost:${PORT}`);
    console.log('API endpoints:');
    console.log('  GET  /api/health          - Health check');
    console.log('  GET  /api/plugins         - List plugins');
    console.log('  GET  /api/globals         - Get globals and snippets');
    console.log('  POST /api/run             - Run all tests');
    console.log('  POST /api/run/:id         - Run single test');
    console.log('  POST /api/validate        - Validate test file');
    console.log('  POST /api/record/start    - Start recording session');
    console.log('  POST /api/record/stop     - Stop recording and get steps');
    console.log('  GET  /api/record/status   - Get recording session status');
    console.log('  POST /api/reports/html    - Generate HTML report');
    console.log('  POST /api/reports/junit   - Generate JUnit XML report');
});
