/**
 * Serveur de test pour les parcours Playwright.
 *
 * Reproduit la topologie de production : nginx sert les fichiers statiques du
 * dashboard et proxifie `/admin/api/*` et `/admin/auth/*` vers l'API (voir
 * apps/admin/nginx.conf). Ici, un petit serveur HTTP joue le rôle de nginx et
 * transmet à la VRAIE application Fastify — routage, cookies, JWT, bcrypt,
 * validation et génération SQL sont donc ceux de production — avec un faux pool
 * à la place de Postgres.
 *
 * Ce qui est réellement exercé : les parcours d'interface, l'authentification,
 * les états de chargement et d'erreur, l'accessibilité au clavier.
 * Ce qui ne l'est pas : la validité du SQL pour Postgres et le comportement de
 * PostGIS — affaire de tests d'intégration, pas de tests d'interface.
 */
import { createServer } from "node:http";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { buildServer } from "../../../api/src/app.js";
import { makeFakePool, rows } from "../../../api/test/helpers/fakePool.js";
import { seedHandlers } from "./fixtures.mjs";

const SITE = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../site");

// Secret de session propre aux tests. Sans lui, signAdminSession() lève et la
// connexion renvoie un 500 opaque — exactement le piège documenté dans
// infra/.env.example pour la production.
process.env.ADMIN_JWT_SECRET ||= "secret-e2e-suffisamment-long-pour-passer-32";

const MIME = {
  ".html": "text/html; charset=utf-8",
  ".js": "text/javascript; charset=utf-8",
  ".css": "text/css; charset=utf-8",
  ".svg": "image/svg+xml",
  ".ico": "image/x-icon",
  ".png": "image/png",
};

/** Les mêmes préfixes que ceux proxifiés par nginx.conf. */
const API_PREFIXES = ["/admin/api/", "/admin/auth/", "/api/"];

export async function startTestServer({ port = 0, handlers = seedHandlers() } = {}) {
  const pool = makeFakePool(handlers);

  const app = await buildServer({
    pool,
    logger: false,
    importToken: "jeton-e2e",
    corsOrigins: ["http://127.0.0.1"],
    seedAdmin: false,
    // Les parcours enchaînent beaucoup d'appels ; les plafonds réels sont
    // vérifiés séparément dans apps/api/test/rateLimit.test.js.
    globalRateLimitMax: 1e9,
    adminRateLimitMax: 1e9,
    authRateLimitMax: 1e9,
  });

  const front = createServer(async (req, res) => {
    const url = req.url || "/";

    if (API_PREFIXES.some((p) => url.startsWith(p))) {
      const chunks = [];
      for await (const c of req) chunks.push(c);
      const payload = chunks.length ? Buffer.concat(chunks) : undefined;

      // `inject()` recalcule content-length à partir du payload : retransmettre
      // celui du client donne un corps tronqué, donc un 400 « champ requis ».
      const headers = { ...req.headers };
      delete headers["content-length"];
      delete headers["transfer-encoding"];

      // inject() traverse tout le cycle de vie Fastify (hooks, plugins,
      // sérialisation) : c'est la même exécution qu'une requête réseau.
      const reply = await app.inject({ method: req.method, url, headers, payload });
      res.writeHead(reply.statusCode, reply.headers);
      res.end(reply.rawPayload);
      return;
    }

    const rel = url.split("?")[0].replace(/^\/+/, "") || "index.html";
    const file = path.resolve(SITE, rel);
    if (!file.startsWith(SITE)) {
      res.writeHead(403).end("forbidden");
      return;
    }
    try {
      const buf = await readFile(file);
      res.writeHead(200, { "content-type": MIME[path.extname(file)] || "application/octet-stream" });
      res.end(buf);
    } catch {
      // Application monopage : toute route inconnue retombe sur index.html
      res.writeHead(200, { "content-type": MIME[".html"] });
      res.end(await readFile(path.join(SITE, "index.html")));
    }
  });

  await new Promise((resolve) => front.listen(port, "127.0.0.1", resolve));

  return {
    app,
    pool,
    url: `http://127.0.0.1:${front.address().port}`,
    /** Reprogramme les réponses de la base entre deux scénarios. */
    setHandlers: (h) => pool.reset(h),
    close: async () => {
      await new Promise((r) => front.close(r));
      await app.close();
    },
  };
}

export { rows };

// Démarrage autonome, utilisé par `webServer` dans playwright.config.js.
if (process.env.E2E_STANDALONE === "1") {
  const srv = await startTestServer({ port: Number(process.env.E2E_PORT || 4319) });
  console.log(`e2e server: ${srv.url}`);
}
