{{ header }}import { spawn } from 'child_process';
import { resolve } from 'path';
import { createConnection } from 'net';

let serverProcess: any;

export async function setup() {
  // Honor a pre-set SUT_URL: when a parent test runner has already
  // started the SUT app, reuse it and skip spawning our own.
  if (process.env.SUT_URL) {
    (globalThis as any).app = {
      async request(path: string, init?: RequestInit): Promise<Response> {
        const url = new URL(path, process.env.SUT_URL);
        return fetch(url.toString(), init);
      },
    };
    return;
  }

  // Spawn the app harness as a subprocess.
  // The harness script loads fixtures and registers handlers for the SUT.
  serverProcess = spawn(
    process.execPath,
    [resolve(__dirname, './app_harness.mjs')],
    { stdio: ['pipe', 'pipe', 'inherit'] }
  );

  const host = '127.0.0.1';
  const port = 8000;
  const url = `http://${host}:${port}`;

  // Poll until the harness actually accepts TCP connections.
  // The harness may print a listening banner before the runtime finishes binding,
  // so port availability is the authoritative readiness signal.
  const deadline = Date.now() + 15000;
  let ready = false;

  while (Date.now() < deadline) {
    if (serverProcess.exitCode !== null || serverProcess.signalCode !== null) {
      // Process died early
      break;
    }

    try {
      const result = await new Promise<boolean>((resolve) => {
        const socket = createConnection({ host, port, timeout: 500 }, () => {
          socket.destroy();
          resolve(true);
        });
        socket.on('error', () => resolve(false));
        socket.on('timeout', () => {
          socket.destroy();
          resolve(false);
        });
      });
      if (result) {
        ready = true;
        break;
      }
    } catch {
      // Ignore and retry
    }

    await new Promise((resolve) => setTimeout(resolve, 100));
  }

  if (!ready) {
    // Process died or timeout
    if (serverProcess && serverProcess.exitCode === null) {
      serverProcess.kill('SIGTERM');
    }
    throw new Error(`App harness did not become reachable on ${host}:${port} within 15s`);
  }

  process.env.SUT_URL = url;

  // Make app available globally to all tests
  (globalThis as any).app = {
    async request(path: string, init?: RequestInit): Promise<Response> {
      const fullUrl = new URL(path, url);
      return fetch(fullUrl.toString(), init);
    },
  };
}

export async function teardown() {
  if (!serverProcess) return;

  const child = serverProcess;
  serverProcess = undefined;

  await new Promise<void>((resolve) => {
    if (child.exitCode !== null || child.signalCode !== null) {
      resolve();
      return;
    }

    const timeout = setTimeout(() => {
      child.kill('SIGKILL');
      resolve();
    }, 5000);

    child.once('close', () => {
      clearTimeout(timeout);
      resolve();
    });

    if (child.stdin) {
      child.stdin.end();
    }
    child.kill('SIGTERM');
  });
}
