import * as http from 'http';
import * as fs from 'fs';
import * as path from 'path';
import { logger } from '../logger.js';

const MIME_TYPES: Record<string, string> = {
  '.html': 'text/html; charset=utf-8',
  '.htm': 'text/html; charset=utf-8',
  '.txt': 'text/plain; charset=utf-8',
  '.md': 'text/plain; charset=utf-8',
  '.json': 'application/json; charset=utf-8',
  '.css': 'text/css; charset=utf-8',
  '.js': 'application/javascript; charset=utf-8',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
};

export interface FixtureServer {
  port: number;
  close(): Promise<void>;
}

/**
 * Serve files under `rootDir` over HTTP on a random localhost port.
 * Path traversal is rejected. Used for benchmark fixtures so tasks
 * that need WebFetch/BrowseWeb stay reproducible without external network.
 */
export async function startFixtureServer(rootDir: string): Promise<FixtureServer> {
  const root = path.resolve(rootDir);
  if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
    throw new Error(`Fixture server root does not exist or is not a directory: ${root}`);
  }

  const server = http.createServer((req, res) => {
    try {
      const url = new URL(req.url ?? '/', 'http://localhost');
      const requested = decodeURIComponent(url.pathname);
      // Resolve and ensure the result still lives under root.
      const resolved = path.resolve(root, '.' + (requested === '/' ? '/index.html' : requested));
      if (!resolved.startsWith(root + path.sep) && resolved !== root) {
        res.statusCode = 403;
        res.end('forbidden');
        return;
      }
      if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
        res.statusCode = 404;
        res.end('not found');
        return;
      }
      const ext = path.extname(resolved).toLowerCase();
      res.statusCode = 200;
      res.setHeader('Content-Type', MIME_TYPES[ext] ?? 'application/octet-stream');
      const stream = fs.createReadStream(resolved);
      stream.pipe(res);
    } catch (err) {
      res.statusCode = 500;
      res.end(`error: ${(err as Error).message}`);
    }
  });

  await new Promise<void>((resolve, reject) => {
    server.once('error', reject);
    server.listen(0, '127.0.0.1', () => {
      server.removeListener('error', reject);
      resolve();
    });
  });

  const address = server.address();
  if (typeof address !== 'object' || address === null) {
    throw new Error('Fixture server did not bind to an address');
  }
  const port = address.port;
  logger.info(`[bench/fixture-server] listening on http://127.0.0.1:${port} root=${root}`);

  return {
    port,
    async close(): Promise<void> {
      await new Promise<void>((resolve) => server.close(() => resolve()));
      logger.info(`[bench/fixture-server] closed port=${port}`);
    },
  };
}
