import { readdir, readFile } from "node:fs/promises";
import { DatabaseSync } from "node:sqlite";

class SqliteD1Statement {
  constructor(database, sql, values = []) {
    this.database = database;
    this.sql = sql;
    this.values = values;
  }

  bind(...values) {
    return new SqliteD1Statement(this.database, this.sql, values);
  }

  async first(columnName) {
    const row = this.database.prepare(this.sql).get(...this.values) ?? null;
    return columnName && row ? row[columnName] : row;
  }

  async all() {
    return { results: this.database.prepare(this.sql).all(...this.values) };
  }

  async run() {
    const result = this.database.prepare(this.sql).run(...this.values);
    return {
      success: true,
      meta: {
        changes: result.changes,
        last_row_id: result.lastInsertRowid,
      },
      results: [],
    };
  }
}

export async function createTestD1(options = {}) {
  const database = new DatabaseSync(":memory:");
  database.exec("PRAGMA foreign_keys = ON;");
  const migrationDirectory = new URL("../../drizzle/", import.meta.url);
  const migrationFiles = (await readdir(migrationDirectory))
    .filter((file) => file.endsWith(".sql"))
    .sort();
  for (const file of migrationFiles) {
    const migration = await readFile(new URL(file, migrationDirectory), "utf8");
    database.exec(migration.replaceAll("--> statement-breakpoint", ""));
  }

  return {
    database,
    d1: {
      prepare(sql) {
        const statement = new SqliteD1Statement(database, sql);
        if (!options.maxBindings) return statement;
        const originalBind = statement.bind.bind(statement);
        statement.bind = (...values) => {
          if (values.length > options.maxBindings) {
            throw new Error(`Too many bound parameters: ${values.length}`);
          }
          return originalBind(...values);
        };
        return statement;
      },
      async batch(statements) {
        database.exec("BEGIN IMMEDIATE");
        try {
          const results = [];
          for (const statement of statements) results.push(await statement.run());
          database.exec("COMMIT");
          return results;
        } catch (error) {
          database.exec("ROLLBACK");
          throw error;
        }
      },
    },
  };
}
