import { execFile } from 'node:child_process'
import { mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { promisify } from 'node:util'
import assert from 'node:assert/strict'
import test from 'node:test'
import { initdbCached } from './index.js'

const execFileAsync = promisify(execFile)

async function makeFakeInitdb(root) {
  const bin = join(root, 'initdb.mjs')
  const countFile = join(root, 'count.txt')
  await writeFile(countFile, '0')
  await writeFile(
    bin,
    `#!/usr/bin/env node
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'

if (process.argv.includes('--version')) {
  console.log('initdb (PostgreSQL) 18.4-test')
  process.exit(0)
}

const dataDir = process.argv[process.argv.indexOf('-D') + 1]
const user = process.argv[process.argv.indexOf('-U') + 1]
const countFile = ${JSON.stringify(countFile)}
const count = Number(readFileSync(countFile, 'utf8')) + 1
writeFileSync(countFile, String(count))
mkdirSync(dataDir, { recursive: true })
writeFileSync(join(dataDir, 'PG_VERSION'), '18')
writeFileSync(join(dataDir, 'inputs.json'), JSON.stringify({ user, args: process.argv.slice(2), lc: process.env.LC_ALL, lang: process.env.LANG }))
`,
    { mode: 0o755 },
  )
  return { bin, countFile }
}

test('copies warm data dirs from a cached template', async () => {
  const root = await mkdtemp(join(tmpdir(), 'pg-template-test-'))
  try {
    const { bin, countFile } = await makeFakeInitdb(root)
    const cache = join(root, 'cache')
    const env = { ...process.env, XDG_CACHE_HOME: cache, LC_ALL: 'C', LANG: 'C' }
    const first = await mkdtemp(join(root, 'data-'))
    const second = await mkdtemp(join(root, 'data-'))

    await initdbCached({ initdbBin: bin, dataDir: first, user: 'test', args: ['-A', 'trust'], env })
    await initdbCached({ initdbBin: bin, dataDir: second, user: 'test', args: ['-A', 'trust'], env })

    assert.equal(await readFile(join(second, 'PG_VERSION'), 'utf8'), '18')
    assert.equal(await readFile(countFile, 'utf8'), '1')
  } finally {
    await rm(root, { recursive: true, force: true })
  }
})

test('falls back to direct initdb when the cache copy fails', async () => {
  const root = await mkdtemp(join(tmpdir(), 'pg-template-test-'))
  try {
    const { bin, countFile } = await makeFakeInitdb(root)
    const cache = join(root, 'cache')
    const env = { ...process.env, XDG_CACHE_HOME: cache, LC_ALL: 'C', LANG: 'C' }
    const first = await mkdtemp(join(root, 'data-'))
    const second = await mkdtemp(join(root, 'data-'))

    await initdbCached({ initdbBin: bin, dataDir: first, user: 'test', args: ['-A', 'trust'], env })
    const templateRoot = join(cache, 'platform-embedded-pg', 'templates')
    const [key] = await readdir(templateRoot)
    await rm(join(templateRoot, key), { recursive: true, force: true })
    await writeFile(join(templateRoot, key), 'not a directory')

    await initdbCached({ initdbBin: bin, dataDir: second, user: 'test', args: ['-A', 'trust'], env })

    assert.equal(await readFile(join(second, 'PG_VERSION'), 'utf8'), '18')
    assert.equal(await readFile(countFile, 'utf8'), '2')
  } finally {
    await rm(root, { recursive: true, force: true })
  }
})

test('a different TZ gets its own template (initdb bakes TZ into postgresql.conf)', async () => {
  const root = await mkdtemp(join(tmpdir(), 'pg-template-test-'))
  try {
    const { bin, countFile } = await makeFakeInitdb(root)
    const cache = join(root, 'cache')
    const base = { ...process.env, XDG_CACHE_HOME: cache, LC_ALL: 'C', LANG: 'C' }
    const first = await mkdtemp(join(root, 'data-'))
    const second = await mkdtemp(join(root, 'data-'))

    await initdbCached({ initdbBin: bin, dataDir: first, user: 'test', args: [], env: { ...base, TZ: 'UTC' } })
    await initdbCached({ initdbBin: bin, dataDir: second, user: 'test', args: [], env: { ...base, TZ: 'Asia/Bangkok' } })

    assert.equal(await readFile(countFile, 'utf8'), '2')
  } finally {
    await rm(root, { recursive: true, force: true })
  }
})

test('a swapped initdb binary with the same version string invalidates the template', async () => {
  const root = await mkdtemp(join(tmpdir(), 'pg-template-test-'))
  try {
    const { bin, countFile } = await makeFakeInitdb(root)
    const cache = join(root, 'cache')
    const env = { ...process.env, XDG_CACHE_HOME: cache, LC_ALL: 'C', LANG: 'C' }
    const first = await mkdtemp(join(root, 'data-'))
    const second = await mkdtemp(join(root, 'data-'))

    await initdbCached({ initdbBin: bin, dataDir: first, user: 'test', args: [], env })
    const source = await readFile(bin, 'utf8')
    await new Promise((r) => setTimeout(r, 10))
    await writeFile(bin, `${source}\n// rebuilt\n`, { mode: 0o755 })
    await initdbCached({ initdbBin: bin, dataDir: second, user: 'test', args: [], env })

    assert.equal(await readFile(countFile, 'utf8'), '2')
  } finally {
    await rm(root, { recursive: true, force: true })
  }
})

test('refuses a non-empty dataDir instead of overlaying it', async () => {
  const root = await mkdtemp(join(tmpdir(), 'pg-template-test-'))
  try {
    const { bin } = await makeFakeInitdb(root)
    const cache = join(root, 'cache')
    const env = { ...process.env, XDG_CACHE_HOME: cache, LC_ALL: 'C', LANG: 'C' }
    const dataDir = await mkdtemp(join(root, 'data-'))
    await writeFile(join(dataDir, 'precious.txt'), 'do not clobber')

    await assert.rejects(
      initdbCached({ initdbBin: bin, dataDir, user: 'test', args: [], env }),
      /dataDir is not empty/,
    )
    assert.equal(await readFile(join(dataDir, 'precious.txt'), 'utf8'), 'do not clobber')
  } finally {
    await rm(root, { recursive: true, force: true })
  }
})

test('red check: fake initdb increments when called directly twice', async () => {
  const root = await mkdtemp(join(tmpdir(), 'pg-template-test-'))
  try {
    const { bin, countFile } = await makeFakeInitdb(root)
    const first = await mkdtemp(join(root, 'data-'))
    const second = await mkdtemp(join(root, 'data-'))
    await execFileAsync(bin, ['-D', first, '-U', 'test', '-A', 'trust'], { env: process.env })
    await execFileAsync(bin, ['-D', second, '-U', 'test', '-A', 'trust'], { env: process.env })
    assert.equal(await readFile(countFile, 'utf8'), '2')
  } finally {
    await rm(root, { recursive: true, force: true })
  }
})
