import { PgBoss } from 'pg-boss'
import { getCursor, runMigrations } from '@znet/catalog-db'
import { loadSyncEnv } from './config.js'
import { createSyncContext } from './context.js'
import { JOBS, syncProductSku, syncReference, type JobName } from './jobs.js'

/**
 * Catalog Sync Service: pg-boss worker that keeps the Catalog Data Layer in step with Magento.
 * Every queue uses the `singleton` policy, so a slow run is never overlapped by the next tick.
 */
const env = loadSyncEnv()
const { ctx, close } = createSyncContext(env)
const { log } = ctx

if (env.MIGRATE_ON_START) await runMigrations(env.DATABASE_URL)

// As the restricted mcp_sync role, the pgboss schema is pre-created (owned by mcp_sync) by `migrate`.
const boss = new PgBoss({ connectionString: env.DATABASE_URL, schema: 'pgboss', application_name: 'mcp-znet-sync', createSchema: env.MIGRATE_ON_START })
boss.on('error', err => log.error({ err }, 'pg-boss error'))
await boss.start()

const schedules: Record<JobName, string> = {
  'sync-reference': env.SYNC_CRON_REFERENCE,
  'sync-products-incremental': env.SYNC_CRON_PRODUCTS,
  'sync-inventory': env.SYNC_CRON_INVENTORY,
  'sync-products-full': env.SYNC_CRON_FULL,
}

for (const [name, run] of Object.entries(JOBS) as Array<[JobName, (typeof JOBS)[JobName]]>) {
  if (name === 'sync-inventory' && !env.CATALOG_STOCK_ENABLED) {
    await boss.unschedule(name).catch(() => {}) // drop a schedule left from an earlier config
    log.info('stock not managed in Magento — inventory sync disabled')
    continue
  }
  await boss.createQueue(name, { policy: 'singleton', retryLimit: 2, retryDelay: 60, retryBackoff: true, expireInSeconds: 60 * 60 * 2 })
  await boss.schedule(name, schedules[name], null, { tz: env.SYNC_TIMEZONE })
  await boss.work(name, { batchSize: 1 }, async () => {
    const stats = await run(ctx)
    log.info({ job: name, stats }, 'job finished')
    return stats
  })
}

// On-demand single-SKU refresh (webhooks in Phase 1.5 enqueue here).
await boss.createQueue('sync-product-sku', { policy: 'stately', retryLimit: 3, retryDelay: 30 })
await boss.work<{ sku: string }>('sync-product-sku', { batchSize: 1 }, async ([job]) => syncProductSku(ctx, job!.data.sku))

// First boot: reference data is needed before products (store scopes), then a full load.
if (!(await getCursor(ctx.db, 'products:all'))) {
  log.info('no product cursor — running reference sync and queueing initial full load')
  await syncReference(ctx)
  await boss.send('sync-products-full', {})
  if (env.CATALOG_STOCK_ENABLED) await boss.send('sync-inventory', {})
}

log.info({ schedules, stock: env.CATALOG_STOCK_ENABLED, tz: env.SYNC_TIMEZONE }, 'sync service started')

async function shutdown(signal: string) {
  log.info({ signal }, 'shutting down')
  await boss.stop({ graceful: true, timeout: 30_000 })
  await close()
  process.exit(0)
}
process.on('SIGTERM', () => void shutdown('SIGTERM'))
process.on('SIGINT', () => void shutdown('SIGINT'))
