{{ header }}import { spawn, ChildProcess } from 'child_process';
import { resolve } from 'path';

let serverProcess: ChildProcess | undefined;

// HTTP client wrapper for making requests to mock server
const createApp = (baseUrl: string) => ({
  async request(path: string, init?: RequestInit): Promise<Response> {
    const url = new URL(path, baseUrl);
    return fetch(url.toString(), init);
  },
});

export async function setup() {
  // Honor a pre-set MOCK_SERVER_URL or SUT_URL: when the test runner (e.g. `alef test-apps
  // run`) has already built and started the shared mock-server, it exports
  // MOCK_SERVER_URL into our environment. In that case, reuse it and skip
  // spawning a local binary entirely. Only self-spawn when both are unset.
  if (process.env.MOCK_SERVER_URL || process.env.SUT_URL) {
    const baseUrl = process.env.MOCK_SERVER_URL || process.env.SUT_URL;
    if (baseUrl) {
      process.env.MOCK_SERVER_URL = baseUrl;
      process.env.SUT_URL = baseUrl;
    }
    (globalThis as unknown as { app: ReturnType<typeof createApp> }).app = createApp(baseUrl!);
    return;
  }

  // Mock server binary must be pre-built (e.g. by CI or `cargo build --manifest-path e2e/rust/Cargo.toml --bin mock-server --release`)
  serverProcess = spawn(
    resolve(__dirname, '../rust/target/release/mock-server'),
    [resolve(__dirname, '../../fixtures')],
    { stdio: ['pipe', 'pipe', 'inherit'] }
  );

  const proc = serverProcess;
  if (!proc) throw new Error('Failed to spawn mock-server process');

  const { url, servers } = await new Promise<{ url: string; servers: Record<string, string> }>((resolveSetup, reject) => {
    let collectedUrl = '';
    let collectedServers: Record<string, string> = {};
    let buffer = '';
    let settled = false;

    const finish = (result: { url: string; servers: Record<string, string> }) => {
      if (settled) return;
      settled = true;
      clearTimeout(startupTimeout);
      proc.stdout.off('data', onData);
      resolveSetup(result);
    };

    const onData = (data: Buffer) => {
      buffer += data.toString();
      const lines = buffer.split('\n');
      buffer = lines.pop() ?? '';
      for (const line of lines) {
        const trimmed = line.trim();
        const urlMatch = trimmed.match(/^MOCK_SERVER_URL=(.*)/);
        if (urlMatch) {
          collectedUrl = urlMatch[1].trim();
        }
        const serversMatch = trimmed.match(/^MOCK_SERVERS=(.*)/);
        if (serversMatch) {
          try { collectedServers = JSON.parse(serversMatch[1].trim()); } catch { /* ignore */ }
          if (collectedUrl) finish({ url: collectedUrl, servers: collectedServers });
        } else if (collectedUrl && trimmed && !trimmed.startsWith('MOCK_SERVER')) {
          finish({ url: collectedUrl, servers: collectedServers });
        }
      }
    };

    const startupTimeout = setTimeout(() => {
      settled = true;
      proc.stdout.off('data', onData);
      reject(new Error('Mock server startup timeout'));
    }, 30000);

    proc.stdout.on('data', onData);
  });

  process.env.MOCK_SERVER_URL = url;
  process.env.SUT_URL = url;
  for (const [fixtureId, fixtureUrl] of Object.entries(servers)) {
    process.env[`MOCK_SERVER_${fixtureId.toUpperCase()}`] = fixtureUrl;
  }
  process.env.MOCK_SERVERS = JSON.stringify(servers);

  // Make app available globally to all tests
  (globalThis as unknown as { app: ReturnType<typeof createApp> }).app = createApp(url);
}

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

  const child = serverProcess;
  serverProcess = undefined;

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

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

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

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