import { promisify } from 'node:util'
import child_process from 'node:child_process'
import { existsSync, mkdirSync } from 'node:fs'
import { writeFile } from 'node:fs/promises'
import { jsPDF } from 'jspdf'

// Dedicated FBA-relabel product labels (sales-order edit page).
//
// Unlike /api/print/product (fixed per-printer layouts), this renders ONE
// dynamic layout that adapts to any label size the user picks:
//   - CODE128 barcode of C_OrderLine.printLabelCode across the full width
//   - article name (small) + the code in cleartext
//   - bottom hint "Label auf SKU: <sku>" so staff know which product gets it
// One page per unit (line qty), all pages at exactly [w, h] mm.
//
// Body: {
//   pathName, fileName,
//   printer,               // CUPS queue name (from CUST_Printer registry)
//   printOptions?,         // CUPS options, defaults to '-o fit-to-page'
//   size: { w, h },        // label size in mm
//   downloadOnly?,         // true → return base64 PDF, skip the printer
//   labels: [{ code, product, sku, qty, barcode (PNG data URL) }]
// }
// Response: { status, content (base64), printResult?, printOk? }

const SAFE_PRINTER = /^[A-Za-z0-9_.-]+$/
const SAFE_OPTIONS = /^[A-Za-z0-9=_.\- ]*$/

const clamp = (v: number, min: number, max: number) => Math.max(min, Math.min(max, v))

const truncateToWidth = (doc: any, text: string, maxWidth: number) => {
  const split = doc.splitTextToSize(String(text || ''), maxWidth)
  if (split.length <= 1) return split[0] || ''
  let first = String(split[0])
  while (first.length > 1 && doc.getTextWidth(first + '…') > maxWidth) {
    first = first.slice(0, -1)
  }
  return first + '…'
}

const drawLabel = (doc: any, label: any, w: number, h: number) => {
  const m = Math.max(1.5, Math.min(w, h) * 0.04)
  const cw = w - 2 * m
  const ch = h - 2 * m

  // Barcode band: ~48% of the content height, capped so very tall labels
  // (e.g. 102×152) don't stretch the bars beyond scannable proportions.
  // Inset horizontally so the bars keep a proper quiet zone (≥ ~3 mm white
  // on both sides) — required for reliable CODE128 scanning.
  const bcQuiet = Math.max(3 - m, cw * 0.04)
  const bcW = cw - 2 * bcQuiet
  const bcH = Math.min(ch * 0.48, bcW * 0.45)
  doc.addImage(label.barcode, 'image/png', m + bcQuiet, m, bcW, bcH)

  const textZoneTop = m + bcH
  const textZone = h - m - textZoneTop

  const nameFs = clamp(ch * 0.28, 5, 9)
  const codeFs = clamp(ch * 0.42, 7, 16)
  const hintFs = clamp(ch * 0.24, 5, 8)

  // Code cleartext directly below the barcode (like Amazon's FNSKU labels)
  doc.setFontSize(codeFs)
  doc.setFont('helvetica', 'bold')
  doc.text(truncateToWidth(doc, label.code, cw), w / 2, textZoneTop + textZone * 0.26, { align: 'center' })

  // Article name (small, one line)
  doc.setFontSize(nameFs)
  doc.setFont('helvetica', 'normal')
  doc.text(truncateToWidth(doc, label.product, cw), w / 2, textZoneTop + textZone * 0.60, { align: 'center' })

  // Bottom line: item condition left + placement hint right.
  // "Neu" is an Amazon FNSKU-label requirement (barcode + product name +
  // condition) — this endpoint only prints FBA relabel labels, so it is fixed.
  const bottomY = textZoneTop + textZone * 0.92
  doc.setFontSize(hintFs)
  doc.setFont('helvetica', 'bold')
  doc.text('NEU', m, bottomY)
  const neuW = doc.getTextWidth('NEU')
  doc.setFont('helvetica', 'normal')
  doc.text(truncateToWidth(doc, `Label auf SKU: ${label.sku || '-'}`, cw - neuW - 2), w - m, bottomY, { align: 'right' })
}

export default defineEventHandler(async (event) => {
  let data: any = {}
  const exec = promisify(child_process.exec)
  const body = await readBody(event)

  const pathName = String(body.pathName || 'relabel-labels').replace(/[^A-Za-z0-9/_.-]/g, '_').replace(/\.\./g, '_')
  const fileName = String(body.fileName || 'relabel-labels.pdf').replace(/[^A-Za-z0-9_.-]/g, '_')
  const filePath = `/root/storage/${pathName}`

  const w = clamp(Number(body?.size?.w) || 57, 20, 300)
  const h = clamp(Number(body?.size?.h) || 32, 20, 300)
  const orientation = w >= h ? 'l' : 'p'
  const downloadOnly = body.downloadOnly === true

  const labels = (body.labels || []).filter((l: any) =>
    l?.barcode && String(l?.code || '').trim())
  if (!labels.length) {
    data['status'] = 400
    data['message'] = 'No printable labels (missing codes or barcodes).'
    return data
  }

  const doc = new jsPDF({
    orientation,
    unit: 'mm',
    format: [w, h],
    putOnlyUsedFonts: true,
    compress: true
  })

  let first = true
  for (const label of labels) {
    const qty = Math.max(1, Number(label.qty || 1))
    for (let i = 0; i < qty; i++) {
      if (!first) doc.addPage([w, h], orientation)
      drawLabel(doc, label, w, h)
      first = false
    }
  }

  const fileContent = doc.output('dataurlstring')?.replace('data:application/pdf;filename=generated.pdf;base64,', '')
  data['status'] = 200
  data['content'] = fileContent
  data['name'] = fileName

  if (!downloadOnly) {
    // Whitelist the CUPS queue/options before shell interpolation (same rules
    // as /api/print/picklist) and report the TRUE outcome — a non-zero `lp`
    // exit means the labels did NOT print.
    const printer = SAFE_PRINTER.test(body.printer || '') ? body.printer : 'default'
    const printOptions = SAFE_OPTIONS.test(body.printOptions ?? '') && body.printOptions
      ? body.printOptions
      : '-o fit-to-page'

    const result: any = { ok: false, printer, stdout: '', stderr: '', error: '' }
    try {
      if (!existsSync(filePath)) {
        mkdirSync(filePath, { recursive: true })
      }
      await writeFile(`${filePath}/${fileName}`, Buffer.from(fileContent, 'base64'))

      const { stdout, stderr } = await exec(`lp -d ${printer} ${printOptions} ${filePath}/${fileName}`)
      result.stdout = stdout || ''
      result.stderr = stderr || ''
      result.ok = true
    } catch (err: any) {
      result.error = err?.stderr || err?.message || 'Error when printing via server side'
      result.stderr = err?.stderr || ''
      result.ok = false
    }
    data['printResult'] = result
    data['printOk'] = result.ok
  }

  return data
})
