import { Umzug, SequelizeStorage } from 'umzug';
import { pathToFileURL } from 'url';
import db from '@/db/models';

/**
 * Migration/seeder runner (replaces sequelize-cli). Migration and seeder files
 * are ESM TypeScript with a default export `{ up, down }` taking
 * `(queryInterface, Sequelize)`. Run via `tsx` (see the `db:*` npm scripts).
 */

type MigrationFn = (queryInterface: unknown, sequelize: unknown) => Promise<void>;

interface MigrationModule {
  up?: MigrationFn;
  down?: MigrationFn;
  default?: { up?: MigrationFn; down?: MigrationFn };
}

const sequelize = db.sequelize;
const queryInterface = sequelize.getQueryInterface();

async function loadMigration(
  filepath: string,
): Promise<{ up?: MigrationFn; down?: MigrationFn }> {
  const mod: MigrationModule = await import(pathToFileURL(filepath).href);
  return mod.default ?? mod;
}

function resolveMigration(params: { name: string; path?: string }) {
  const filepath = params.path;
  if (!filepath) {
    throw new Error(`Migration ${params.name} has no resolvable path`);
  }

  return {
    // Strip the extension so tracking is stable across tsx (.ts) and compiled
    // (.js) runs.
    name: params.name.replace(/\.(ts|js)$/, ''),
    up: async () => {
      const migration = await loadMigration(filepath);
      if (migration.up) {
        await migration.up(queryInterface, db.Sequelize);
      }
    },
    down: async () => {
      const migration = await loadMigration(filepath);
      if (migration.down) {
        await migration.down(queryInterface, db.Sequelize);
      }
    },
  };
}

export const migrator = new Umzug({
  migrations: {
    glob: ['migrations/*.{ts,js}', { cwd: import.meta.dirname }],
    resolve: resolveMigration,
  },
  context: queryInterface,
  storage: new SequelizeStorage({ sequelize }),
  logger: console,
});

export const seeder = new Umzug({
  migrations: {
    glob: ['seeders/*.{ts,js}', { cwd: import.meta.dirname }],
    resolve: resolveMigration,
  },
  context: queryInterface,
  storage: new SequelizeStorage({ sequelize, tableName: 'SequelizeData' }),
  logger: console,
});

const commands: Record<string, () => Promise<unknown>> = {
  'migrate:up': () => migrator.up(),
  'migrate:down': () => migrator.down(),
  'migrate:pending': async () => {
    const pending = await migrator.pending();
    console.log(pending.map((m) => m.name));
  },
  'seed:up': () => seeder.up(),
  'seed:down': () => seeder.down(),
};

async function runCli(): Promise<void> {
  const command = process.argv[2] ?? '';
  const handler = commands[command];
  if (!handler) {
    console.error(
      `Unknown command "${command}". Available: ${Object.keys(commands).join(', ')}`,
    );
    process.exit(1);
  }
  await handler();
  await sequelize.close();
}

const isMain =
  Boolean(process.argv[1]) &&
  import.meta.url === pathToFileURL(process.argv[1]).href;

if (isMain) {
  runCli().catch((error) => {
    console.error(error);
    process.exit(1);
  });
}
