import { AlephaError } from "alepha";
import { sql } from "drizzle-orm";
import type { BunSQLDatabase } from "drizzle-orm/bun-sql";
import type { PgDatabase } from "drizzle-orm/pg-core";
import { PostgresProvider } from "./PostgresProvider.ts";

/**
 * Bun PostgreSQL provider using Drizzle ORM with Bun's native SQL client.
 *
 * This provider uses Bun's built-in SQL class for PostgreSQL connections,
 * which provides excellent performance on the Bun runtime.
 *
 * @example
 * ```ts
 * // Set DATABASE_URL environment variable
 * // DATABASE_URL=postgres://user:password@localhost:5432/database
 *
 * // Or configure programmatically
 * alepha.with({
 *   provide: DatabaseProvider,
 *   use: BunPostgresProvider,
 * });
 * ```
 */
export class BunPostgresProvider extends PostgresProvider {
  protected client?: Bun.SQL;
  protected bunDb?: BunSQLDatabase;

  /**
   * Get the Drizzle Postgres database instance.
   */
  public override get db(): PgDatabase<any> {
    if (!this.bunDb) {
      throw new AlephaError("Database not initialized");
    }

    return this.bunDb as unknown as PgDatabase<any>;
  }

  protected override async executeMigrations(
    migrationsFolder: string,
  ): Promise<void> {
    if (this.schema !== "public") {
      await this.db.execute(
        sql.raw(`SET search_path TO ${this.schema}, public`),
      );
    }
    const { migrate } = await import("drizzle-orm/bun-sql/migrator");
    await migrate(this.bunDb!, {
      migrationsFolder,
      migrationsTable: this.migrationsTable,
    });
  }

  // -------------------------------------------------------------------------------------------------------------------

  public async connect(): Promise<void> {
    this.log.debug("Connect ..");

    // Check if we're running in Bun
    if (typeof Bun === "undefined") {
      throw new AlephaError(
        "BunPostgresProvider requires the Bun runtime. Use NodePostgresProvider for Node.js.",
      );
    }

    const { drizzle } = await import("drizzle-orm/bun-sql");

    // Create Bun SQL client with pool options
    // Set search_path via connection URL so all pool connections use the correct schema
    let connectionUrl = this.url;
    if (this.schema !== "public") {
      const separator = connectionUrl.includes("?") ? "&" : "?";
      connectionUrl += `${separator}search_path=${this.schema},public`;
    }
    const bunOptions: Record<string, any> = { url: connectionUrl };
    if (this.pgEnv.POOL_MAX != null) {
      bunOptions.max = this.pgEnv.POOL_MAX;
    }
    if (this.pgEnv.POOL_IDLE_TIMEOUT != null) {
      bunOptions.idleTimeout = this.pgEnv.POOL_IDLE_TIMEOUT;
    }
    if (this.pgEnv.POOL_CONNECT_TIMEOUT != null) {
      bunOptions.connectionTimeout = this.pgEnv.POOL_CONNECT_TIMEOUT;
    }
    this.client = new Bun.SQL(bunOptions);

    // Test connection
    await this.client.unsafe("SELECT 1");

    this.bunDb = drizzle({
      client: this.client,
      logger: {
        logQuery: (query: string, params: unknown[]) => {
          this.log.trace(query, { params });
        },
      },
    });

    this.log.info("Connection OK");
  }

  public async close(): Promise<void> {
    if (this.client) {
      this.log.debug("Close...");

      await this.client.close();

      this.client = undefined;
      this.bunDb = undefined;

      this.log.info("Connection closed");
    }
  }
}
