import { describe, it, expect } from "vitest";
import { makeFakePool, rows } from "./helpers/fakePool.js";

/**
 * Méta-tests du harnais.
 *
 * Presque toutes les assertions du projet s'appuient sur `makeFakePool` :
 * si ce double se met à tout avaler en silence (un `find` qui renvoie toujours
 * undefined, un `throws` ignoré, un compteur qui ne s'incrémente pas), des
 * dizaines de tests passeraient au vert sans plus rien vérifier.
 *
 * Ces tests vérifient donc l'outil de mesure lui-même.
 */
describe("makeFakePool — enregistrement", () => {
  it("enregistre chaque requête avec son SQL et ses paramètres", async () => {
    const pool = makeFakePool();
    await pool.query("SELECT 1 FROM bands WHERE ma_id = $1", [42]);
    expect(pool.calls).toHaveLength(1);
    expect(pool.calls[0].sql).toContain("FROM bands");
    expect(pool.calls[0].values).toEqual([42]);
  });

  it("normalise l'absence de paramètres en tableau vide", async () => {
    const pool = makeFakePool();
    await pool.query("SELECT 1");
    expect(pool.calls[0].values).toEqual([]);
  });

  it("accepte la forme objet { text }", async () => {
    const pool = makeFakePool();
    await pool.query({ text: "SELECT 1 FROM bands" }, []);
    expect(pool.calls[0].sql).toBe("SELECT 1 FROM bands");
  });
});

describe("makeFakePool — recherche", () => {
  it("find() trouve par sous-chaîne ET renvoie undefined quand rien ne matche", async () => {
    const pool = makeFakePool();
    await pool.query("UPDATE bands SET name = $1", ["x"]);

    expect(pool.find("UPDATE bands")).toBeDefined();
    // Le cas qui compte : un find() qui renverrait toujours undefined ferait
    // passer au vert tous les `expect(pool.find(...)).toBeUndefined()`.
    expect(pool.find("DELETE FROM bands")).toBeUndefined();
  });

  it("find() accepte une RegExp", async () => {
    const pool = makeFakePool();
    await pool.query("SELECT a, b FROM bands");
    expect(pool.find(/SELECT\s+a,\s*b/)).toBeDefined();
    expect(pool.find(/SELECT\s+z/)).toBeUndefined();
  });

  it("findAll() renvoie toutes les occurrences, pas seulement la première", async () => {
    const pool = makeFakePool();
    await pool.query("INSERT INTO bands VALUES (1)");
    await pool.query("INSERT INTO bands VALUES (2)");
    await pool.query("SELECT 1");
    expect(pool.findAll("INSERT INTO bands")).toHaveLength(2);
    expect(pool.findAll("DELETE")).toHaveLength(0);
  });

  it("sequence() reflète l'ordre réel d'exécution", async () => {
    const pool = makeFakePool();
    await pool.query("BEGIN");
    await pool.query("UPDATE bands SET x = 1");
    await pool.query("COMMIT");
    expect(pool.sequence(["COMMIT", "BEGIN", "UPDATE bands"]))
      .toEqual(["BEGIN", "UPDATE bands", "COMMIT"]);
  });
});

describe("makeFakePool — handlers", () => {
  it("renvoie le résultat du premier handler qui matche", async () => {
    const pool = makeFakePool([
      { match: "FROM bands", result: rows({ ma_id: 1 }) },
      { match: "FROM bands", result: rows({ ma_id: 2 }) },
    ]);
    expect((await pool.query("SELECT * FROM bands")).rows[0].ma_id).toBe(1);
  });

  it("renvoie un résultat vide quand aucun handler ne matche", async () => {
    const pool = makeFakePool([{ match: "FROM autre", result: rows({ x: 1 }) }]);
    expect(await pool.query("SELECT * FROM bands")).toEqual({ rows: [], rowCount: 0 });
  });

  // Si `throws` était ignoré, tous les tests de chemin d'erreur (rollback,
  // 500 sans fuite d'information) passeraient sans rien exercer.
  it("throws fait bien lever la requête", async () => {
    const pool = makeFakePool([{ match: "FROM bands", throws: new Error("boum") }]);
    await expect(pool.query("SELECT * FROM bands")).rejects.toThrow("boum");
  });

  it("result peut être une fonction du SQL et des paramètres", async () => {
    const pool = makeFakePool([
      { match: "FROM bands", result: (_sql, values) => rows({ echo: values[0] }) },
    ]);
    expect((await pool.query("SELECT * FROM bands WHERE id=$1", [7])).rows[0].echo).toBe(7);
  });

  it("rows() produit un rowCount cohérent", () => {
    expect(rows()).toEqual({ rows: [], rowCount: 0 });
    expect(rows({ a: 1 }, { a: 2 }).rowCount).toBe(2);
  });
});

describe("makeFakePool — transactions", () => {
  it("connect() renvoie un client qui partage le journal des requêtes", async () => {
    const pool = makeFakePool();
    const client = await pool.connect();
    await client.query("SELECT 1 FROM bands");
    expect(pool.find("FROM bands")).toBeDefined();
  });

  it("détecte COMMIT et ROLLBACK séparément", async () => {
    const committed = makeFakePool();
    const c1 = await committed.connect();
    await c1.query("COMMIT");
    expect(committed.committed).toBe(true);
    expect(committed.rolledBack).toBe(false);

    const rolled = makeFakePool();
    const c2 = await rolled.connect();
    await c2.query("ROLLBACK");
    expect(rolled.rolledBack).toBe(true);
    expect(rolled.committed).toBe(false);
  });

  it("compte les connexions ouvertes et libérées", async () => {
    const pool = makeFakePool();
    const client = await pool.connect();
    expect(pool.connections).toBe(1);
    expect(pool.released).toBe(0);
    client.release();
    expect(pool.released).toBe(1);
  });

  it("un pool neuf ne rapporte ni commit ni rollback", () => {
    const pool = makeFakePool();
    expect(pool.committed).toBe(false);
    expect(pool.rolledBack).toBe(false);
    expect(pool.connections).toBe(0);
    expect(pool.released).toBe(0);
  });
});
