import { describe, expect, test } from "bun:test";
import {
  batch,
  createDb,
  createRepository,
  databaseHealth,
  retryTransaction,
} from "../src/index.ts";
import type { Driver, Row } from "../src/index.ts";

function memoryDriver(): Driver {
  const rows: Row[] = [{ id: 1, name: "One" }];
  return {
    dialect: "sqlite",
    async query(sql) {
      if (/COUNT/.test(sql)) return [{ count: rows.length }];
      if (/SELECT 1 AS healthy/.test(sql)) return [{ healthy: 1 }];
      return rows;
    },
    async exec() {
      return { changes: 1, lastInsertId: 2 };
    },
    async transaction(callback) {
      return callback(this);
    },
    close() {},
  };
}

describe("database helper kit", () => {
  test("provides repository CRUD helpers and health checks", async () => {
    const db = createDb(memoryDriver());
    const repository = createRepository<{ id: number; name: string }>(db, {
      table: "items",
      allowedColumns: ["name"],
    });
    expect((await repository.find(1))?.name).toBe("One");
    expect(await repository.count()).toBe(1);
    expect((await databaseHealth(db)).ok).toBe(true);
    expect(batch([1, 2, 3], 2)).toEqual([[1, 2], [3]]);
  });

  test("retries transaction callbacks with bounded attempts", async () => {
    const db = createDb(memoryDriver());
    let calls = 0;
    const value = await retryTransaction(
      db,
      async () => {
        calls += 1;
        if (calls < 2) throw new Error("retry");
        return "done";
      },
      { attempts: 2, baseDelayMs: 0, shouldRetry: () => true },
    );
    expect(value).toBe("done");
    expect(calls).toBe(2);
  });

  test("uses dialect-aware placeholders and bounded list options", async () => {
    const queries: string[] = [];
    const driver = memoryDriver();
    driver.dialect = "postgres";
    const originalQuery = driver.query;
    driver.query = async (sql, params) => {
      queries.push(sql);
      return originalQuery.call(driver, sql, params);
    };
    const originalExec = driver.exec;
    driver.exec = async (sql, params) => {
      queries.push(sql);
      return originalExec.call(driver, sql, params);
    };
    const repository = createRepository<{ id: number; name: string }>(createDb(driver), {
      table: "items",
      allowedColumns: ["name"],
      maxListLimit: 50,
    });
    await repository.find(1);
    await repository.create({ name: "Two" });
    await repository.update(1, { name: "Changed" });
    await repository.all({ limit: 10, offset: 5, orderBy: "name", direction: "desc" });
    expect(queries.some((sql) => sql.includes("id = $1"))).toBe(true);
    expect(queries.some((sql) => sql.includes("VALUES ($1)"))).toBe(true);
    expect(queries.some((sql) => sql.includes("LIMIT $1 OFFSET $2"))).toBe(true);
  });

  test("automatically applies an immutable tenant scope to every repository operation", async () => {
    const calls: Array<{ sql: string; params: unknown[] }> = [];
    const driver: Driver = {
      dialect: "sqlite",
      async query(sql, params = []) {
        calls.push({ sql, params: [...params] });
        return /COUNT/.test(sql) ? [{ count: 0 }] : [];
      },
      async exec(sql, params = []) {
        calls.push({ sql, params: [...params] });
        return { changes: 1 };
      },
      async transaction(callback) {
        return callback(this);
      },
      close() {},
    };
    type RecordRow = { id: number; name: string; tenant_id: string };
    const repository = createRepository<RecordRow>(createDb(driver), {
      table: "records",
      allowedColumns: ["name"],
      scope: { column: "tenant_id", value: "acme" },
    });
    await repository.all();
    await repository.find(1);
    await repository.create({ name: "A", tenant_id: "other" });
    await repository.update(1, { name: "B" });
    await repository.remove(1);
    await repository.count();
    expect(calls.every((call) => call.sql.includes("tenant_id"))).toBe(true);
    expect(calls.every((call) => call.params.includes("acme"))).toBe(true);
    expect(calls.some((call) => call.params.includes("other"))).toBe(false);
  });

  test("rejects unsafe repository identifiers", () => {
    const db = createDb(memoryDriver());
    expect(() => createRepository(db, { table: "items; DROP TABLE items" })).toThrow(
      "Unsafe SQL identifier",
    );
  });
});
