import { describe, test, expect } from "bun:test";
import { getDummyConnection } from "../helper";
import { WhereBuilder, type WhereQuery } from "data/entities/query/WhereBuilder";

function qb() {
   const c = getDummyConnection();
   const kysely = c.dummyConnection.kysely;
   return kysely.selectFrom("t").selectAll();
}
function compile(q: WhereQuery) {
   const { sql, parameters } = WhereBuilder.addClause(qb(), q).compile();
   return { sql, parameters };
}

describe("WhereBuilder", () => {
   test("single validation", () => {
      const tests: [WhereQuery, string, any[]][] = [
         [{ name: "Michael", age: 40 }, '("name" = ? and "age" = ?)', ["Michael", 40]],
         [{ name: { $eq: "Michael" } }, '"name" = ?', ["Michael"]],
         [{ int: { $between: [1, 2] } }, '"int" between ? and ?', [1, 2]],
         [{ val: { $isnull: 1 } }, '"val" is null', []],
         [{ val: { $isnull: true } }, '"val" is null', []],
         [{ val: { $isnull: 0 } }, '"val" is not null', []],
         [{ val: { $isnull: false } }, '"val" is not null', []],
         [{ val: { $like: "what" } }, '"val" like ?', ["what"]],
         [{ val: { $like: "w*t" } }, '"val" like ?', ["w%t"]],
      ];

      for (const [query, expectedSql, expectedParams] of tests) {
         const { sql, parameters } = compile(query);
         expect(sql).toContain(`select * from "t" where ${expectedSql}`);
         expect(parameters).toEqual(expectedParams);
      }
   });

   test("multiple validations", () => {
      const tests: [WhereQuery, string, any[]][] = [
         // multiple constraints per property
         [{ val: { $lt: 10, $gte: 3 } }, '("val" < ? and "val" >= ?)', [10, 3]],
         [{ val: { $lt: 10, $gte: 3 } }, '("val" < ? and "val" >= ?)', [10, 3]],
         [{ val: { $lt: 10, $gte: 3 } }, '("val" < ? and "val" >= ?)', [10, 3]],

         // multiple properties
         [
            { val1: { $eq: "foo" }, val2: { $eq: "bar" } },
            '("val1" = ? and "val2" = ?)',
            ["foo", "bar"],
         ],
         [
            { val1: { $eq: "foo" }, val2: { $eq: "bar" } },
            '("val1" = ? and "val2" = ?)',
            ["foo", "bar"],
         ],

         // or constructs
         [
            { $or: { val1: { $eq: "foo" }, val2: { $eq: "bar" } } },
            '("val1" = ? or "val2" = ?)',
            ["foo", "bar"],
         ],
         [{ val1: { $eq: 1 }, $or: { val1: { $eq: 2 } } }, '("val1" = ? or "val1" = ?)', [1, 2]],
         [{ val1: { $eq: 1 }, $or: { val1: { $eq: 2 } } }, '("val1" = ? or "val1" = ?)', [1, 2]],
      ];

      for (const [query, expectedSql, expectedParams] of tests) {
         const { sql, parameters } = compile(query);
         expect(sql).toContain(`select * from "t" where ${expectedSql}`);
         expect(parameters).toEqual(expectedParams);
      }
   });

   test("keys", () => {
      const tests: [WhereQuery, string[]][] = [
         // multiple constraints per property
         [{ val: { $lt: 10, $gte: 3 } }, ["val"]],

         // multiple properties
         [{ val1: { $eq: "foo" }, val2: { $eq: "bar" } }, ["val1", "val2"]],

         // or constructs
         [{ $or: { val1: { $eq: "foo" }, val2: { $eq: "bar" } } }, ["val1", "val2"]],
         [{ val1: { $eq: 1 }, $or: { val1: { $eq: 2 } } }, ["val1"]],
      ];

      for (const [query, expectedKeys] of tests) {
         const keys = WhereBuilder.getPropertyNames(query);
         expect(keys).toEqual(expectedKeys);
      }
   });
});
