import { FileMigrationProvider, Kysely, Migrator } from 'kysely';
import { PostgresJSDialect } from 'kysely-postgres-js';
import { mkdir, readdir } from 'node:fs/promises';
import { join } from 'node:path';
import { parse } from 'pg-connection-string';
import postgres, { Notice } from 'postgres';
import { GenericContainer, Wait } from 'testcontainers';
import { DataSource } from 'typeorm';

const globalSetup = async () => {
  const postgresContainer = await new GenericContainer('tensorchord/pgvecto-rs:pg14-v0.2.0')
    .withExposedPorts(5432)
    .withEnvironment({
      POSTGRES_PASSWORD: 'postgres',
      POSTGRES_USER: 'postgres',
      POSTGRES_DB: 'immich',
    })
    .withCommand([
      'postgres',
      '-c',
      'shared_preload_libraries=vectors.so',
      '-c',
      'search_path="$$user", public, vectors',
      '-c',
      'max_wal_size=2GB',
      '-c',
      'shared_buffers=512MB',
      '-c',
      'fsync=off',
      '-c',
      'full_page_writes=off',
      '-c',
      'synchronous_commit=off',
    ])
    .withWaitStrategy(Wait.forAll([Wait.forLogMessage('database system is ready to accept connections', 2)]))
    .start();

  const postgresPort = postgresContainer.getMappedPort(5432);
  const postgresUrl = `postgres://postgres:postgres@localhost:${postgresPort}/immich`;
  process.env.IMMICH_TEST_POSTGRES_URL = postgresUrl;

  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-expect-error
  const modules = import.meta.glob('/src/migrations/*.ts', { eager: true });

  const config = {
    type: 'postgres' as const,
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-expect-error
    migrations: Object.values(modules).map((module) => Object.values(module)[0]),
    migrationsRun: false,
    synchronize: false,
    connectTimeoutMS: 10_000, // 10 seconds
    parseInt8: true,
    url: postgresUrl,
  };

  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-expect-error
  const dataSource = new DataSource(config);
  await dataSource.initialize();
  await dataSource.runMigrations();
  await dataSource.destroy();

  // for whatever reason, importing from test/utils causes vitest to crash
  // eslint-disable-next-line unicorn/prefer-module
  const migrationFolder = join(__dirname, '..', 'schema/migrations');
  // TODO remove after we have at least one kysely migration
  await mkdir(migrationFolder, { recursive: true });

  const parsed = parse(process.env.IMMICH_TEST_POSTGRES_URL!);

  const parsedOptions = {
    ...parsed,
    ssl: false,
    host: parsed.host ?? undefined,
    port: parsed.port ? Number(parsed.port) : undefined,
    database: parsed.database ?? undefined,
  };

  const driverOptions = {
    ...parsedOptions,
    onnotice: (notice: Notice) => {
      if (notice['severity'] !== 'NOTICE') {
        console.warn('Postgres notice:', notice);
      }
    },
    max: 10,
    types: {
      date: {
        to: 1184,
        from: [1082, 1114, 1184],
        serialize: (x: Date | string) => (x instanceof Date ? x.toISOString() : x),
        parse: (x: string) => new Date(x),
      },
      bigint: {
        to: 20,
        from: [20],
        parse: (value: string) => Number.parseInt(value),
        serialize: (value: number) => value.toString(),
      },
    },
    connection: {
      TimeZone: 'UTC',
    },
  };

  const db = new Kysely({
    dialect: new PostgresJSDialect({ postgres: postgres({ ...driverOptions, max: 1, database: 'postgres' }) }),
  });

  // TODO just call `databaseRepository.migrate()` (probably have to wait until TypeOrm is gone)
  const migrator = new Migrator({
    db,
    migrationLockTableName: 'kysely_migrations_lock',
    migrationTableName: 'kysely_migrations',
    provider: new FileMigrationProvider({
      fs: { readdir },
      path: { join },
      migrationFolder,
    }),
  });

  const { error } = await migrator.migrateToLatest();
  if (error) {
    console.error('Unable to run kysely migrations', error);
    throw error;
  }

  await db.destroy();
};

export default globalSetup;
