// Predeploy migration gate.
//
// Run as a Cloud Deploy predeploy hook (a K8s Job using the service's released image).
// Blocks the rollout when there are pending migrations the image expects to have been applied.
//
// Exit codes:
//   0  nothing pending, or all pending migrations are non-blocking
//   1  one or more blocking pending migrations - rollout must not proceed
//   2  the check itself failed (e.g. Mongo unreachable) - fail closed
//
// A migration is non-blocking when either:
//   - it is tagged "nonblocking" (preferred; safe to set on an already-applied migration), or
//   - its filename ends with "_nonBlocking" before the extension,
//     e.g. 20260101000000_backfill_thing_nonBlocking.mjs.

import path from 'node:path'
import { fileURLToPath } from 'node:url'
import east from 'east'

const { MigrationManager } = east

const NON_BLOCKING_SUFFIX = '_nonBlocking'
const NON_BLOCKING_TAG = 'nonblocking'
const TAG = process.env.MIGRATION_TAG || 'saas'

const scriptDir = path.dirname(fileURLToPath(import.meta.url))
const migrationsDir = path.resolve(scriptDir, '..')

function skipRequested() {
  const value = process.env.SKIP_MIGRATION_CHECK
  return Boolean(value) && value !== '0' && value !== 'false'
}

async function findBlockingMigrations() {
  // east resolves .eastrc, the migrations dir and the adapter relative to the
  // working directory (same as `npm run migrations`). chdir so the check works
  // regardless of how the container invokes it.
  process.chdir(migrationsDir)
  // The adapter aborts unless a tag is passed on argv; we pass the tag to east
  // directly, so disable that CLI-only guard.
  process.env.SKIP_TAG_CHECK = '1'

  const manager = new MigrationManager()
  await manager.configure({ esModules: true })
  await manager.connect()

  try {
    const pending = await manager.getMigrationNames({ status: 'new' })
    const candidates = pending.filter(
      name => !name.endsWith(NON_BLOCKING_SUFFIX)
    )
    const nonBlocking = pending.filter(name =>
      name.endsWith(NON_BLOCKING_SUFFIX)
    )
    const blocking = candidates.length
      ? await manager.getMigrationNames({
          migrations: candidates,
          tag: `${TAG} & !${NON_BLOCKING_TAG}`,
        })
      : []

    return { pending, nonBlocking, blocking }
  } finally {
    await manager.disconnect()
  }
}

async function main() {
  if (skipRequested()) {
    console.warn(
      `SKIP_MIGRATION_CHECK is set ("${process.env.SKIP_MIGRATION_CHECK}") - ` +
        'bypassing the pending-migration gate. Only use this for incident recovery.'
    )
    return 0
  }

  const { pending, nonBlocking, blocking } = await findBlockingMigrations()

  if (nonBlocking.length) {
    console.log(
      `Ignoring ${nonBlocking.length} non-blocking pending migration(s):\n` +
        nonBlocking.map(name => `  - ${name}`).join('\n')
    )
  }

  if (blocking.length === 0) {
    console.log(
      pending.length === 0
        ? `No pending "${TAG}" migrations. Safe to deploy.`
        : `No blocking pending "${TAG}" migrations. Safe to deploy.`
    )
    return 0
  }

  console.error(
    `${blocking.length} blocking pending "${TAG}" migration(s) must be applied before deploying:\n` +
      blocking.map(name => `  - ${name}`).join('\n') +
      `\n\nApply them, then retry the rollout. To intentionally ship a non-blocking ` +
      `migration, add the "${NON_BLOCKING_TAG}" tag (preferred — safe on already-applied ` +
      `migrations) or rename the file with the "${NON_BLOCKING_SUFFIX}" suffix.`
  )
  return 1
}

main()
  .then(code => process.exit(code))
  .catch(err => {
    console.error('Migration check failed; blocking rollout (fail closed):')
    console.error(err)
    process.exit(2)
  })
