import { promises as fs } from 'node:fs';
import { exists } from 'node:fs/promises';
import * as path from 'node:path';
import { join } from 'node:path';
import { PGlite } from '@electric-sql/pglite';
import {
  CamelCasePlugin,
  FileMigrationProvider,
  Kysely,
  Migrator,
} from 'kysely';
import codegen from 'kysely-codegen';
import { KyselyPGlite } from 'kysely-pglite';
import type { DB } from './v1.generated';

const root = join(__dirname, '..');
async function migrate(client: PGlite) {
  console.log('migrating pglite ....');
  const glob = new Bun.Glob('initial/*.sql');
  for await (const migration of glob.scan(root)) {
    await client.exec(await Bun.file(join(root, migration)).text());
  }
}

async function migrateKysely(kysely: Kysely<any>) {
  console.log('init migrating kysely ....');
  const migrator = new Migrator({
    db: kysely,
    migrationTableSchema: 'core',
    migrationTableName: 'kysely_migration',
    migrationLockTableName: 'kysely_migration_lock',
    provider: new FileMigrationProvider({
      fs,
      path,
      // This needs to be an absolute path.
      migrationFolder: path.join(root, 'migrations'),
    }),
  });

  console.log('migrating kysely ....');
  const { error, results } = await migrator.migrateToLatest();

  for (const it of results ?? []) {
    if (it.status === 'Success') {
      console.log(`migration "${it.migrationName}" was executed successfully`);
    } else if (it.status === 'Error') {
      console.error(`failed to execute migration "${it.migrationName}"`);
    }
  }

  if (error) {
    console.error('failed to migrate');
    console.error(error);
    process.exit(1);
  }
}

const dumpPath = join(root, '.data.tar');
const dumpFile = (async () => {
  const dataExists = await exists(dumpPath);
  if (!dataExists) {
    console.log('Creating initial dump');
    const main = new PGlite();
    await migrate(main);
    const { dialect } = new KyselyPGlite(main);
    const kysely = new Kysely<DB>({
      dialect,
      plugins: [new CamelCasePlugin()],
    });
    await migrateKysely(kysely);
    await codegen.generate({
      db: kysely,
      outFile: path.join(root, 'src/v1.generated.ts'),
      defaultSchemas: ['xxx'],
      camelCase: true,
      dialect: codegen.getDialect('postgres'),
    });
    const content = await main.dumpDataDir('none');
    await Bun.write(dumpPath, content);
    return content;
  }
  return Bun.file(dumpPath);
})();

export async function useLocalKysely() {
  let isDestroyed = false;
  const now = performance.now();

  const dump = await dumpFile;

  const pglite = new PGlite({ loadDataDir: dump });
  // warm up
  await pglite.sql`SELECT 1`;
  const { dialect } = new KyselyPGlite(pglite);
  const kysely = new Kysely<DB>({
    dialect,
    plugins: [new CamelCasePlugin()],
  });

  const originalDestroy = kysely.destroy.bind(kysely);
  kysely.destroy = async () => {
    if (isDestroyed) return;
    isDestroyed = true;
    const destroyTime = performance.now();
    await originalDestroy().catch(() => undefined);
    await pglite.close().catch(() => undefined);
    console.log(
      `Database destroyed: ${Math.round(performance.now() - destroyTime)}ms`
    );
  };

  console.log(`Database initialized: ${Math.round(performance.now() - now)}ms`);

  return kysely;
}
