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'
import fetchHelper from '../../utils/fetchHelper'
import getTokenHelper from '../../utils/getTokenHelper'
import { drawCode128, fitTextLine } from '../../utils/labelBarcode'
import { resolveLabelMedia, type ResolvedMedia } from '../../utils/labelMedia'
import { mediaOption } from '../../utils/cupsMedia'

// ---------------------------------------------------------------------------
// Return label — POST /api/print/return-label
//
// ONE label per return (not one per article/qty): the label identifies the return
// document, the barcode encodes its number (RMA DocumentNo for an RMA return —
// what /mobile/reconditioning scans — or the customer-return document number),
// and every returned article is listed with its quantity. When the lines don't
// fit, continuation labels ("Seite 2/2") of the same format follow.
//
// Body: { organizationId, kind: 'rma' | 'customer-return', documentNo,
//         partnerName?, reason?, locatorCode?, orgName?,
//         lines: [{ name, value?, sku?, qty, locatorCode? }],
//         labelPrinterId?: 'a'..'f' (m_product.labelprinter_rma), downloadOnly?,
//         mediaOverride?: { w, h }  (downloadOnly previews only) }
//
// Page size = the printer's real label size (resolveLabelMedia: app registry →
// CUPS queue default → legacy per-printer fallback) and the layout adapts:
//   tiny     h < 24 mm   (35×17)   doc no + barcode + company · totals
//   compact  h < 40 mm   (54×29)   company/date · doc no · barcode · first lines
//   standard h ≥ 40 mm   (74×52, 56×75, 102×152) header band, doc no, barcode,
//                                  partner/reason, article table, footer
// Printing = same mechanics as /api/print/product (lp, fit-to-page, explicit
// media only for registry sizes). Company name = ad_org.companyname (fetched
// server-side by organizationId; falls back to body.orgName).
// ---------------------------------------------------------------------------

const LABEL_PRINTERS: Record<string, string> = {
  a: 'labelprinter-1', b: 'labelprinter-2', c: 'labelprinter-3',
  d: 'labelprinter-4', e: 'labelprinter-5', f: 'labelprinter-6'
}
const DEFAULT_QUEUE = 'labelprinter-2'
// Legacy formats (mm) when no media can be resolved (dev without CUPS) — the
// 2026-09 prod PPD defaults.
const LEGACY_FORMAT: Record<string, [number, number]> = {
  a: [102, 152], b: [35, 17], c: [56, 75], d: [102, 152], e: [102, 152], f: [54, 29]
}
const SAFE_QUEUE = /^[A-Za-z0-9_.-]+$/
const PT_MM = 0.3528

const s = (v: any) => (v === undefined || v === null) ? '' : String(v).replace(/\s+/g, ' ').trim()
const berlinNow = () => new Date().toLocaleString('de-DE', {
  day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Berlin'
}).replace(',', '')

interface LabelLine { name: string; value: string; qty: number; locatorCode: string }
interface LabelData {
  kind: 'rma' | 'customer-return'
  documentNo: string
  title: string
  companyName: string
  partnerName: string
  reason: string
  locatorCode: string
  date: string
  lines: LabelLine[]
  totalQty: number
}

/** Fit a single text line into maxW (shrinks font to minFs, then ellipsises). */
const putFit = (doc: any, text: string, x: number, y: number, maxW: number, maxFs: number, minFs: number, opts: any = {}) => {
  const { text: str, fontSize } = fitTextLine(doc, text, maxW, maxFs, minFs)
  doc.setFontSize(fontSize)
  doc.text(str, x, y, { baseline: 'top', ...opts })
  return fontSize * PT_MM
}

const lineLabel = (l: LabelLine) => [l.value, l.name].filter(Boolean).join('  ')

// ---- layouts ---------------------------------------------------------------

const drawTiny = (doc: any, W: number, H: number, d: LabelData) => {
  const m = 1.5
  let y = m
  doc.setFont('helvetica', 'bold')
  y += putFit(doc, `${d.title} ${d.documentNo}`, m, y, W - 2 * m, 9, 6) + 0.6
  const bottomH = 2.4
  const bcH = Math.max(5, H - y - bottomH - m - 0.8)
  drawCode128(doc, d.documentNo, m, y, W - 2 * m, bcH, { quietMm: 1.5, maxModuleMm: 0.5 })
  y += bcH + 0.6
  doc.setFont('helvetica', 'normal')
  doc.setFontSize(5)
  const totals = `${d.lines.length} Pos. · ${d.totalQty} Stk`
  const tw = doc.getTextWidth(totals)
  doc.text(totals, W - m, y, { baseline: 'top', align: 'right' })
  putFit(doc, d.companyName, m, y, W - 2 * m - tw - 1.5, 5, 4)
  return d.lines.length // everything "listed" as totals — no continuation
}

// Compact labels never paginate: what fits is listed, the rest is summarised.
const drawCompact = (doc: any, W: number, H: number, d: LabelData) => {
  const m = 2
  let y = m
  // company · date
  doc.setFont('helvetica', 'normal')
  doc.setFontSize(6)
  const dw = doc.getTextWidth(d.date)
  doc.text(d.date, W - m, y, { baseline: 'top', align: 'right' })
  doc.setFont('helvetica', 'bold')
  putFit(doc, d.companyName.toUpperCase(), m, y, W - 2 * m - dw - 2, 6, 4.5)
  y += 6 * PT_MM + 0.8
  doc.setDrawColor(0); doc.setLineWidth(0.25); doc.line(m, y, W - m, y); y += 0.8
  // doc no
  y += putFit(doc, `${d.title} ${d.documentNo}`, m, y, W - 2 * m, 12, 8) + 0.8
  // barcode
  const bcH = H >= 32 ? 9 : 7.5
  drawCode128(doc, d.documentNo, m, y, W - 2 * m, bcH, { quietMm: 2, maxModuleMm: 0.5 })
  y += bcH + 0.8
  // partner (only when it leaves room for the articles) + article rows
  const rowH = 6 * PT_MM + 0.5
  const limitY = H - m
  if (d.partnerName && d.lines.length <= 2 && limitY - y > rowH * (d.lines.length + 1)) {
    doc.setFont('helvetica', 'italic')
    y += putFit(doc, `${d.partnerName}${d.reason ? ' · ' + d.reason : ''}`, m, y, W - 2 * m, 6, 5) + 0.5
  }
  doc.setFont('helvetica', 'normal')
  let shown = 0
  while (shown < d.lines.length && y + rowH <= limitY) {
    const remaining = d.lines.length - shown
    // the last row that fits while more remain → summary line instead
    if (remaining > 1 && y + 2 * rowH > limitY) {
      doc.setFont('helvetica', 'italic')
      putFit(doc, `+ ${remaining} weitere Pos. · ${d.totalQty} Stk gesamt`, m, y, W - 2 * m, 6, 5)
      break
    }
    const l = d.lines[shown]
    doc.setFont('helvetica', 'bold')
    doc.setFontSize(6)
    doc.text(`${l.qty}×`, m + 4, y, { baseline: 'top', align: 'right' })
    doc.setFont('helvetica', 'normal')
    putFit(doc, lineLabel(l), m + 5, y, W - 2 * m - 5, 6, 5)
    y += rowH
    shown++
  }
  return d.lines.length
}

const drawStandard = (doc: any, W: number, H: number, d: LabelData, start: number, page: number, pages: number) => {
  const big = H >= 100
  const m = big ? 4 : 2.5
  let y = 0
  // header band: company (white on black) · date
  const bandH = big ? 9 : 6.5
  doc.setFillColor(20, 20, 20)
  doc.rect(0, 0, W, bandH, 'F')
  doc.setTextColor(255, 255, 255)
  doc.setFont('helvetica', 'normal')
  doc.setFontSize(big ? 8 : 6)
  const dw = doc.getTextWidth(d.date)
  doc.text(d.date, W - m, (bandH - (big ? 8 : 6) * PT_MM) / 2, { baseline: 'top', align: 'right' })
  doc.setFont('helvetica', 'bold')
  const cfs = fitTextLine(doc, d.companyName.toUpperCase(), W - 2 * m - dw - 3, big ? 12 : 8, 5)
  doc.setFontSize(cfs.fontSize)
  doc.text(cfs.text, m, (bandH - cfs.fontSize * PT_MM) / 2, { baseline: 'top' })
  doc.setTextColor(0, 0, 0)
  y = bandH + (big ? 3 : 1.6)

  // title + document number
  doc.setFont('helvetica', 'normal')
  doc.setFontSize(big ? 9 : 6.5)
  doc.text(d.title, m, y, { baseline: 'top' })
  y += (big ? 9 : 6.5) * PT_MM + (big ? 1 : 0.4)
  doc.setFont('helvetica', 'bold')
  y += putFit(doc, d.documentNo, m, y, W - 2 * m, big ? 30 : 17, 10) + (big ? 2 : 1)

  // barcode
  const bcH = big ? 18 : (H >= 60 ? 12 : 10)
  drawCode128(doc, d.documentNo, m, y, W - 2 * m, bcH, { quietMm: 3, maxModuleMm: big ? 0.6 : 0.5 })
  y += bcH + (big ? 2 : 1.2)

  // partner · reason · locator
  const infoFs = big ? 9 : 6.5
  doc.setFont('helvetica', 'normal')
  const info: string[] = []
  if (d.partnerName) info.push(d.partnerName)
  if (d.reason) info.push(d.reason)
  if (d.locatorCode) info.push('Lagerplatz ' + d.locatorCode)
  if (info.length) y += putFit(doc, info.join('  ·  '), m, y, W - 2 * m, infoFs, 5) + (big ? 1.5 : 0.8)

  // article table
  doc.setDrawColor(0); doc.setLineWidth(big ? 0.35 : 0.25); doc.line(m, y, W - m, y); y += big ? 1.2 : 0.7
  const rowFs = big ? 9 : (H >= 60 ? 7 : 6.5)
  const rowH = rowFs * PT_MM + (big ? 1.6 : 0.8)
  const footerH = rowFs * PT_MM + (big ? 1.5 : 1)
  const qtyW = big ? 11 : 7
  const valW = W >= 70 ? (big ? 24 : 16) : 0
  // header row
  doc.setFont('helvetica', 'bold'); doc.setFontSize(rowFs - 1)
  doc.text('Stk', m + qtyW - 1, y, { baseline: 'top', align: 'right' })
  doc.text('Artikel', m + qtyW + 1, y, { baseline: 'top' })
  if (valW) doc.text('Art.-Nr.', W - m, y, { baseline: 'top', align: 'right' })
  y += rowH * 0.9
  doc.setLineWidth(0.15); doc.line(m, y - 0.4, W - m, y - 0.4)

  let shown = start
  const limitY = H - m - footerH
  while (shown < d.lines.length && y + rowH <= limitY) {
    const l = d.lines[shown]
    doc.setFont('helvetica', 'bold'); doc.setFontSize(rowFs)
    doc.text(String(l.qty), m + qtyW - 1, y, { baseline: 'top', align: 'right' })
    doc.setFont('helvetica', 'normal')
    let nameW = W - 2 * m - qtyW - 1
    if (valW && l.value) {
      doc.setFontSize(rowFs - 0.5)
      const vf = fitTextLine(doc, l.value, valW, rowFs - 0.5, 5)
      doc.setFontSize(vf.fontSize)
      doc.text(vf.text, W - m, y, { baseline: 'top', align: 'right' })
      nameW -= valW + 1.5
    }
    putFit(doc, valW ? l.name : lineLabel(l), m + qtyW + 1, y, nameW, rowFs, Math.max(5, rowFs - 1.5))
    y += rowH
    shown++
  }

  // footer: totals · page
  doc.setLineWidth(0.25); doc.line(m, H - m - footerH, W - m, H - m - footerH)
  doc.setFont('helvetica', 'normal'); doc.setFontSize(rowFs - 1)
  const fy = H - m - footerH + (big ? 1 : 0.6)
  doc.text(`${d.lines.length} Pos. · ${d.totalQty} Stk`, m, fy, { baseline: 'top' })
  if (pages > 1 || shown < d.lines.length) {
    doc.text(`Seite ${page}/${pages}`, W - m, fy, { baseline: 'top', align: 'right' })
  } else if (d.kind === 'rma') {
    doc.text('RMA', W - m, fy, { baseline: 'top', align: 'right' })
  }
  return shown
}

// Two-pass render: first count how many lines each page holds (dry run on a scratch
// doc of the same size), then draw with the real page count.
const renderLabel = (format: [number, number], d: LabelData) => {
  const orientation: 'l' | 'p' = format[0] >= format[1] ? 'l' : 'p'
  const make = () => new jsPDF({ orientation, unit: 'mm', format, putOnlyUsedFonts: true, compress: true })
  const [W, H] = format
  const layout = H < 24 ? 'tiny' : (H < 40 ? 'compact' : 'standard')
  const draw = (doc: any, start: number, page: number, pages: number) =>
    layout === 'tiny' ? drawTiny(doc, W, H, d)
      : layout === 'compact' ? drawCompact(doc, W, H, d)
        : drawStandard(doc, W, H, d, start, page, pages)

  // pass 1: paginate
  const breaks: number[] = []
  {
    const scratch = make()
    let start = 0
    let guard = 0
    do {
      const shown = draw(scratch, start, breaks.length + 1, 99)
      if (shown <= start && start < d.lines.length) { breaks.push(d.lines.length); break } // nothing fits → stop
      breaks.push(shown)
      start = shown
      if (start < d.lines.length) scratch.addPage(format, orientation)
    } while (start < d.lines.length && ++guard < 50)
  }
  const pages = Math.max(1, breaks.length)
  // pass 2: draw
  const doc = make()
  let start = 0
  for (let p = 0; p < pages; p++) {
    if (p > 0) doc.addPage(format, orientation)
    draw(doc, start, p + 1, pages)
    start = breaks[p]
  }
  return { doc, pages, orientation }
}

// ---- handler ---------------------------------------------------------------

export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const kind: 'rma' | 'customer-return' = body?.kind === 'customer-return' ? 'customer-return' : 'rma'
  const documentNo = s(body?.documentNo)
  if (!documentNo) {
    setResponseStatus(event, 400)
    return { ok: false, status: 400, message: 'documentNo is required' }
  }
  const downloadOnly = body?.downloadOnly === true
  const labelPrinterId = String(body?.labelPrinterId || 'b')
  const queue = LABEL_PRINTERS[labelPrinterId] || (SAFE_QUEUE.test(body?.queue || '') ? body.queue : DEFAULT_QUEUE)

  // Company name — authoritative from ad_org (fail-soft to what the client knows)
  let companyName = s(body?.orgName)
  const orgId = Number(body?.organizationId)
  if (orgId > 0) {
    try {
      const token = await getTokenHelper(event)
      const org: any = await fetchHelper(event, `models/ad_org/${orgId}?$select=companyname,Name`, 'GET', token, null)
      companyName = s(org?.companyname || org?.CompanyName || org?.Name) || companyName
    } catch (e) {
      console.warn('[print/return-label] ad_org lookup failed, using client orgName:', (e as any)?.message || e)
    }
  }

  const rawLines: any[] = Array.isArray(body?.lines) ? body.lines : []
  const lines: LabelLine[] = rawLines
    .map((l) => ({ name: s(l?.name), value: s(l?.value || l?.sku), qty: Number(l?.qty) || 0, locatorCode: s(l?.locatorCode) }))
    .filter((l) => l.qty > 0 && (l.name || l.value))
  const locatorCodes = [...new Set(lines.map((l) => l.locatorCode).filter(Boolean))]
  const data: LabelData = {
    kind,
    documentNo,
    title: kind === 'rma' ? 'RETOURE' : 'KUNDENRÜCKSENDUNG',
    companyName: companyName || 'LogShip',
    partnerName: s(body?.partnerName),
    reason: s(body?.reason),
    locatorCode: s(body?.locatorCode) || (locatorCodes.length === 1 ? locatorCodes[0] : ''),
    date: berlinNow(),
    lines,
    totalQty: lines.reduce((sum, l) => sum + l.qty, 0)
  }

  // Label size: registry → CUPS → legacy (or an explicit preview override)
  let media: ResolvedMedia | null = null
  if (downloadOnly && Number(body?.mediaOverride?.w) > 0 && Number(body?.mediaOverride?.h) > 0) {
    media = { w: Number(body.mediaOverride.w), h: Number(body.mediaOverride.h), source: 'cups' }
  } else {
    try { media = await resolveLabelMedia(event, queue) } catch { media = null }
  }
  const format: [number, number] = media ? [media.w, media.h] : (LEGACY_FORMAT[labelPrinterId] || [74, 52])

  let rendered
  try {
    rendered = renderLabel(format, data)
  } catch (err: any) {
    console.error('[print/return-label] render failed:', err)
    setResponseStatus(event, 500)
    return { ok: false, status: 500, message: 'Etikett konnte nicht erzeugt werden: ' + (err?.message || err) }
  }
  const content = rendered.doc.output('dataurlstring')?.replace('data:application/pdf;filename=generated.pdf;base64,', '')
  const result: any = {
    ok: true, status: 200, printed: false, printer: queue, labelPrinterId,
    media, format, pages: rendered.pages, lines: lines.length, totalQty: data.totalQty, companyName: data.companyName,
    content
  }
  if (downloadOnly) return result

  // Print (same mechanics as /api/print/product)
  const options = ['-o fit-to-page']
  if (media?.source === 'registry') options.push(mediaOption(media))
  const dir = '/root/storage/return-labels'
  const safeDoc = documentNo.replace(/[^A-Za-z0-9_-]/g, '') || 'return'
  const fileName = `return-label-${kind}-${safeDoc}-${labelPrinterId}-${Date.now()}.pdf`
  try {
    if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
    await writeFile(`${dir}/${fileName}`, Buffer.from(content, 'base64'))
    const exec = promisify(child_process.exec)
    const { stdout, stderr } = await exec(`lp -d ${queue} ${options.join(' ')} ${dir}/${fileName}`)
    result.stdout = stdout || ''
    result.stderr = stderr || ''
    result.printed = true
    result.options = options.join(' ')
  } catch (err: any) {
    result.ok = false
    result.status = 500
    result.message = 'Druck fehlgeschlagen: ' + (err?.stderr || err?.message || err)
    setResponseStatus(event, 500)
  }
  delete result.content
  return result
})
