import type { Database } from "bun:sqlite";
import { mkdir } from "node:fs/promises";
import { dirname } from "node:path";
import {
  $atom,
  $env,
  $hook,
  $inject,
  $state,
  AlephaError,
  type Static,
  z,
} from "alepha";
import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
import type { PgDatabase } from "drizzle-orm/pg-core";
import { databaseEnvSchema } from "../../schemas/databaseEnvSchema.ts";
import { SqliteModelBuilder } from "../../services/SqliteModelBuilder.ts";
import { DatabaseProvider, type SQLLike } from "./DatabaseProvider.ts";

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

const envSchema = databaseEnvSchema;

/**
 * Configuration options for the Bun SQLite database provider.
 */
export const bunSqliteOptions = $atom({
  name: "alepha.postgres.bun-sqlite.options",
  schema: z.object({
    path: z
      .string()
      .describe(
        "Filepath or :memory:. If empty, provider will use DATABASE_URL from env.",
      )
      .optional(),
  }),
  default: {},
});

export type BunSqliteProviderOptions = Static<typeof bunSqliteOptions.schema>;

declare module "alepha" {
  interface State {
    [bunSqliteOptions.key]: BunSqliteProviderOptions;
  }
}

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

/**
 * Bun SQLite provider using Drizzle ORM with Bun's native SQLite client.
 *
 * This provider uses Bun's built-in `bun:sqlite` for SQLite connections,
 * which provides excellent performance on the Bun runtime.
 *
 * @example
 * ```ts
 * // Set DATABASE_URL environment variable
 * // DATABASE_URL=sqlite://./my-database.db
 *
 * // Or configure programmatically
 * alepha.with({
 *   provide: DatabaseProvider,
 *   use: BunSqliteProvider,
 * });
 *
 * // Or use options atom
 * alepha.store.mut(bunSqliteOptions, (old) => ({
 *   ...old,
 *   path: ":memory:",
 * }));
 * ```
 */
export class BunSqliteProvider extends DatabaseProvider {
  protected readonly env = $env(envSchema);
  protected readonly builder = $inject(SqliteModelBuilder);
  protected readonly options = $state(bunSqliteOptions);

  protected sqlite?: Database;
  protected bunDb?: BunSQLiteDatabase;

  public get name() {
    return "sqlite";
  }

  public override readonly dialect = "sqlite";

  public override get url(): string {
    const path = this.options.path ?? this.env.DATABASE_URL;
    if (path) {
      if (path.startsWith("postgres://")) {
        throw new AlephaError(
          "Postgres URL is not supported for SQLite provider.",
        );
      }
      return path;
    }

    if (this.alepha.isTest() || this.alepha.isServerless()) {
      return ":memory:";
    } else {
      return "node_modules/.alepha/bun-sqlite.db";
    }
  }

  public override get db(): PgDatabase<any> {
    if (!this.bunDb) {
      throw new AlephaError("Database not initialized");
    }

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

  public override get nativeConnection(): unknown {
    return this.sqlite;
  }

  public override async execute(
    query: SQLLike,
  ): Promise<Array<Record<string, unknown>>> {
    return (this.bunDb as BunSQLiteDatabase).all(query);
  }

  protected readonly onStart = $hook({
    on: "start",
    handler: async () => {
      // Check if we're running in Bun
      if (typeof Bun === "undefined") {
        throw new AlephaError(
          "BunSqliteProvider requires the Bun runtime. Use NodeSqliteProvider for Node.js.",
        );
      }

      const { Database } = await import("bun:sqlite");
      const { drizzle } = await import("drizzle-orm/bun-sqlite");

      const filepath = this.url.replace("sqlite://", "").replace("sqlite:", "");

      if (filepath !== ":memory:" && filepath !== "") {
        const dir = dirname(filepath);
        if (dir) {
          await mkdir(dir, { recursive: true }).catch(() => null);
        }
      }

      this.sqlite = new Database(filepath);

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

      // Never migrate in serverless mode - migrations should be applied during deployment
      if (!this.alepha.isServerless()) {
        await this.migrate();
      }

      this.log.info(`Using Bun SQLite database at ${filepath}`);
    },
  });

  protected readonly onStop = $hook({
    on: "stop",
    priority: "last",
    handler: async () => {
      if (this.sqlite) {
        this.log.debug("Closing Bun SQLite connection...");
        this.sqlite.close();
        this.sqlite = undefined;
        this.bunDb = undefined;
        this.log.info("Bun SQLite connection closed");
      }
    },
  });

  protected async executeMigrations(migrationsFolder: string): Promise<void> {
    const { migrate } = await import("drizzle-orm/bun-sqlite/migrator");
    await migrate(this.bunDb!, { migrationsFolder });
  }
}
