import { app } from 'electron'
import { spawn } from 'child_process'
import { readFile, writeFile, mkdir } from 'fs/promises'
import { join } from 'path'

const EXTRA_PATH_DIRS = ['/opt/homebrew/bin', '/usr/local/bin', '/opt/local/bin']
const ENV_WITH_DDEV_PATH = {
  ...process.env,
  PATH: [...EXTRA_PATH_DIRS, process.env.PATH].join(':')
}

interface WarmupMarker {
  ddevVersion: string
}

function markerPath(): string {
  return join(app.getPath('userData'), 'image-warmup.json')
}

async function readMarker(): Promise<WarmupMarker | null> {
  try {
    return JSON.parse(await readFile(markerPath(), 'utf-8')) as WarmupMarker
  } catch {
    return null
  }
}

async function writeMarker(marker: WarmupMarker): Promise<void> {
  await mkdir(app.getPath('userData'), { recursive: true })
  await writeFile(markerPath(), JSON.stringify(marker), 'utf-8')
}

function run(cmd: string, args: string[]): Promise<string> {
  return new Promise((resolve, reject) => {
    const child = spawn(cmd, args, { env: ENV_WITH_DDEV_PATH, stdio: ['ignore', 'pipe', 'pipe'] })
    let out = ''
    child.stdout.on('data', (d: Buffer) => (out += d.toString()))
    child.stderr.on('data', (d: Buffer) => (out += d.toString()))
    child.on('error', reject)
    child.on('close', (code) => (code === 0 ? resolve(out) : reject(new Error(out))))
  })
}

// `ddev version` reports the exact default image tags (webserver, db,
// router, ssh-agent, xhgui) for whatever DDEV release is installed, so this
// stays correct across DDEV upgrades without hardcoding image names here.
async function getDefaultImages(): Promise<{ version: string; images: string[] }> {
  const out = await run('ddev', ['version', '--json-output'])
  const lastLine = out.trim().split('\n').pop() ?? '{}'
  const envelope = JSON.parse(lastLine) as { raw?: Record<string, string> }
  const raw = envelope.raw ?? {}
  const version = raw['DDEV version'] ?? 'unknown'
  const images = Object.values(raw).filter((v) => /^[\w-]+\/[\w.-]+:[\w.-]+$/.test(v))
  return { version, images: [...new Set(images)] }
}

// Pre-pulls DDEV's core Docker images (webserver, db, router, ssh-agent) in
// the background so a first-ever `ddev start` doesn't stall on a multi-GB
// download the moment someone tries to use a freshly installed app. Runs
// once per DDEV version — re-warms automatically after a DDEV upgrade,
// since image tags are versioned. No-ops quietly if ddev/docker aren't
// available yet; a real `ddev start` will surface that error properly.
export async function warmupDdevImagesIfNeeded(): Promise<void> {
  try {
    const { version, images } = await getDefaultImages()
    const marker = await readMarker()
    if (marker?.ddevVersion === version) return

    for (const image of images) {
      try {
        await run('docker', ['pull', image])
      } catch (err) {
        console.error(`[image warmup] failed to pull ${image}:`, err)
      }
    }

    await writeMarker({ ddevVersion: version })
  } catch (err) {
    console.error('[image warmup] skipped:', err)
  }
}
