/**
 * Test Server Utility
 * Provides standardized test server management for all SuperDangerous applications
 */
import { spawn } from "child_process";
import { execSync } from "child_process";
import { createLogger } from "../core/index.js";
let logger; // Will be initialized when needed
function ensureLogger() {
    if (!logger) {
        logger = createLogger("TestServer");
    }
    return logger;
}
export class TestServer {
    process = null;
    config;
    startPromise = null;
    port;
    constructor(config) {
        this.port = config.port;
        this.config = {
            apiBase: `http://localhost:${config.port}`,
            env: {},
            startupTimeout: 30000,
            healthEndpoint: "/api/health",
            silent: true,
            ...config,
        };
    }
    /**
     * Clean up any existing processes on the port
     */
    cleanupPort() {
        try {
            if (process.platform === "win32") {
                // Windows: Find and kill process using the port
                execSync(`netstat -ano | findstr :${this.config.port} | findstr LISTENING`, { stdio: "ignore" });
                const result = execSync(`netstat -ano | findstr :${this.config.port} | findstr LISTENING`).toString();
                const lines = result.split("\n");
                for (const line of lines) {
                    const parts = line.trim().split(/\s+/);
                    const pid = parts[parts.length - 1];
                    if (pid && pid !== "0") {
                        execSync(`taskkill /F /PID ${pid}`, { stdio: "ignore" });
                    }
                }
            }
            else {
                // Unix-like: Use lsof to find and kill process
                execSync(`lsof -ti:${this.config.port} | xargs kill -9 2>/dev/null || true`, { stdio: "ignore" });
            }
        }
        catch (_error) {
            // Port might already be free, ignore errors
        }
    }
    /**
     * Wait for the server to be ready
     */
    async waitForServer() {
        const startTime = Date.now();
        const { apiBase, healthEndpoint, startupTimeout } = this.config;
        const healthUrl = `${apiBase}${healthEndpoint}`;
        if (!this.config.silent) {
            console.log(`Waiting for server at ${healthUrl}...`);
        }
        while (Date.now() - startTime < startupTimeout) {
            try {
                const controller = new AbortController();
                const timeoutId = setTimeout(() => controller.abort(), 1000);
                const response = await fetch(healthUrl, {
                    signal: controller.signal,
                });
                clearTimeout(timeoutId);
                if (response.ok) {
                    if (!this.config.silent) {
                        console.log(`Server is ready at ${healthUrl}`);
                    }
                    return; // Server is ready
                }
            }
            catch (_error) {
                // Server not ready yet
            }
            // Check if process has exited
            if (this.process && this.process.exitCode !== null) {
                throw new Error(`Server process exited with code ${this.process.exitCode}`);
            }
            await new Promise((resolve) => setTimeout(resolve, 500));
        }
        throw new Error(`Server failed to start within ${startupTimeout}ms`);
    }
    /**
     * Start the test server
     */
    async start() {
        // Return existing promise if already starting
        if (this.startPromise) {
            return this.startPromise;
        }
        // Already running
        if (this.process) {
            return;
        }
        this.startPromise = this._start();
        return this.startPromise;
    }
    async _start() {
        // Clean up any existing processes
        this.cleanupPort();
        // Determine command based on entry point extension
        const { entryPoint } = this.config;
        const isTypeScript = entryPoint.endsWith(".ts");
        const command = isTypeScript ? "npx" : "node";
        const args = isTypeScript ? ["tsx", entryPoint] : [entryPoint];
        // Start the server process
        this.process = spawn(command, args, {
            env: {
                ...process.env,
                ...this.config.env,
                NODE_ENV: "test",
                PORT: String(this.config.port),
                API_PORT: String(this.config.port),
                SILENT_STARTUP: "1",
            },
            stdio: this.config.silent ? ["ignore", "pipe", "pipe"] : "inherit",
        });
        // Handle process output if not silent
        if (this.config.silent && this.process.stdout && this.process.stderr) {
            this.process.stdout.on("data", (data) => {
                const output = data.toString();
                // Only log errors or important messages
                if (output.includes("ERROR") || output.includes("WARN")) {
                    ensureLogger().debug(`Server output: ${output}`);
                }
            });
            this.process.stderr.on("data", (data) => {
                ensureLogger().error(`Server error: ${data}`);
            });
        }
        // Handle process exit
        this.process.on("exit", (code) => {
            if (code !== 0 && code !== null) {
                ensureLogger().error(`Server process exited with code ${code}`);
            }
        });
        // Wait for server to be ready
        await this.waitForServer();
        ensureLogger().debug(`Test server started on port ${this.config.port}`);
    }
    /**
     * Stop the test server
     */
    async stop() {
        if (!this.process) {
            return;
        }
        return new Promise((resolve) => {
            if (!this.process) {
                resolve();
                return;
            }
            const cleanup = () => {
                this.process = null;
                this.startPromise = null;
                resolve();
            };
            // Set a timeout for graceful shutdown
            const killTimeout = setTimeout(() => {
                if (this.process) {
                    this.process.kill("SIGKILL");
                }
                cleanup();
            }, 5000);
            this.process.on("exit", () => {
                clearTimeout(killTimeout);
                cleanup();
            });
            // Try graceful shutdown first
            this.process.kill("SIGTERM");
        });
    }
    /**
     * Get the API base URL
     */
    getApiBase() {
        return this.config.apiBase;
    }
    /**
     * Make a request to the test server
     */
    async request(endpoint, options = {}) {
        const url = `${this.config.apiBase}${endpoint}`;
        const response = await fetch(url, {
            ...options,
            headers: {
                "Content-Type": "application/json",
                ...options.headers,
            },
        });
        const text = await response.text();
        let data;
        try {
            data = JSON.parse(text);
        }
        catch {
            data = text;
        }
        return {
            status: response.status,
            ok: response.ok,
            data,
            response,
        };
    }
}
/**
 * Create a test server instance
 */
export function createTestServer(config) {
    return new TestServer(config);
}
/**
 * Global test server instance for Jest
 */
let globalTestServer = null;
/**
 * Setup function for Jest beforeAll
 */
export async function setupTestServer(config) {
    globalTestServer = new TestServer(config);
    await globalTestServer.start();
}
/**
 * Teardown function for Jest afterAll
 */
export async function teardownTestServer() {
    if (globalTestServer) {
        await globalTestServer.stop();
        globalTestServer = null;
    }
}
/**
 * Get the global test server instance
 */
export function getTestServer() {
    return globalTestServer;
}
//# sourceMappingURL=TestServer.js.map