import { spawn, type ChildProcess } from 'node:child_process'
import { mkdtemp, rm } from 'node:fs/promises'
import { createServer } from 'node:net'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { createRequire } from 'node:module'
import { initdbCached } from '@tooling/pg-template'
import { drizzle } from 'drizzle-orm/node-postgres'
import type { TransactionalDatabase } from '@platform-modules/db'
import pg from 'pg'
import { pushSchema } from './migrate.js'
import { ordersSchema, type OrdersSchema } from './schema.js'

function embeddedPlatformPackage(): string {
  const { platform, arch } = process
  if (platform === 'linux' && arch === 'x64') return 'linux-x64'
  if (platform === 'linux' && arch === 'arm64') return 'linux-arm64'
  if (platform === 'linux' && arch === 'arm') return 'linux-arm'
  if (platform === 'linux' && arch === 'ia32') return 'linux-ia32'
  if (platform === 'linux' && arch === 'ppc64') return 'linux-ppc64'
  if (platform === 'darwin' && arch === 'arm64') return 'darwin-arm64'
  if (platform === 'darwin' && arch === 'x64') return 'darwin-x64'
  if (platform === 'win32' && arch === 'x64') return 'windows-x64'
  throw new Error(`unsupported embedded-postgres platform: ${platform}-${arch}`)
}

function resolveBinDir(): string {
  const require = createRequire(import.meta.url)
  const pkg = `@embedded-postgres/${embeddedPlatformPackage()}`
  const entry = require.resolve(pkg)
  return join(dirname(entry), '..', 'native', 'bin')
}

const PG_ENV = { ...process.env, LC_ALL: 'C', LANG: 'C' }

async function getEphemeralPort(): Promise<number> {
  return new Promise((resolve, reject) => {
    const server = createServer()
    server.listen(0, '127.0.0.1', () => {
      const addr = server.address()
      if (!addr || typeof addr === 'string') {
        server.close()
        reject(new Error('failed to allocate ephemeral port'))
        return
      }
      const port = addr.port
      server.close((err) => {
        if (err) reject(err)
        else resolve(port)
      })
    })
    server.on('error', reject)
  })
}

async function waitForPostgres(pool: pg.Pool, attempts = 60, delayMs = 250): Promise<void> {
  let lastErr: unknown
  for (let i = 0; i < attempts; i++) {
    try {
      await pool.query('SELECT 1')
      return
    } catch (e) {
      lastErr = e
      await new Promise((r) => setTimeout(r, delayMs))
    }
  }
  throw lastErr instanceof Error ? lastErr : new Error(String(lastErr))
}

export async function startPg(): Promise<{
  db: TransactionalDatabase<OrdersSchema>
  pool: pg.Pool
  stop: () => Promise<void>
}> {
  const binDir = resolveBinDir()
  const ext = process.platform === 'win32' ? '.exe' : ''
  const initdbBin = join(binDir, `initdb${ext}`)
  const postgresBin = join(binDir, `postgres${ext}`)
  const dataDir = await mkdtemp(join(tmpdir(), 'commerce-orders-pg-'))
  const port = await getEphemeralPort()
  const user = 'test'

  await initdbCached({ initdbBin, dataDir, user, args: ['-A', 'trust', '--no-sync'], env: PG_ENV })

  let proc: ChildProcess | undefined
  await new Promise<void>((resolve, reject) => {
    proc = spawn(
      postgresBin,
      ['-D', dataDir, '-p', String(port), '-k', dataDir, '-c', 'listen_addresses='],
      { stdio: 'ignore', env: PG_ENV },
    )
    proc.on('error', reject)
    proc.once('spawn', () => resolve())
  })

  const pool = new pg.Pool({
    host: dataDir,
    port,
    user,
    database: 'postgres',
  })

  await waitForPostgres(pool)

  const db = drizzle(pool, { schema: ordersSchema }) as TransactionalDatabase<OrdersSchema>
  await pushSchema(db)

  const stop = async () => {
    await pool.end().catch(() => undefined)
    if (proc && !proc.killed) {
      proc.kill('SIGKILL')
    }
    await rm(dataDir, { recursive: true, force: true })
  }

  return { db, pool, stop }
}
