'use strict'
// ── Factures fournisseurs ──────────────────────────────────────────────────
// Courriels to:factures@ (reçus dans cc@, membre du groupe) → OCR des pièces (image/PDF) → un INTAKE par pièce
// stocké dans ERPNext (doctype « Supplier Invoice Intake », auditable/permissionné) + pièce jointe versée dans ERPNext.
// Revue dans OPS → création d'un Purchase Invoice DRAFT (jamais soumis auto). ERPNext = registre ; OPS = capture/triage.
const http = require('http')
const cfg = require('./config')
const { log, json, parseBody, loadSeenSet, saveSeenSet } = require('./helpers')
const erp = require('./erp')

const SEEN = '/app/data/supplier_invoices_seen.json'
const DOCTYPE = 'Supplier Invoice Intake'
const INVOICE_TO = () => (process.env.GMAIL_INVOICE_TO || 'factures@targointernet.com').trim()
const isOcrable = (a) => /pdf$/i.test(a.mimeType) || /^image\//i.test(a.mimeType) || /\.(pdf|jpe?g|png|webp|heic)$/i.test(a.filename || '')

// Verse la pièce dans ERPNext (File privé attaché à l'intake) → le champ `attachment` reçoit l'URL. Best-effort.
async function attachToErp (name, filename, mime, base64) {
  const r = await erp.create('File', {
    file_name: filename || (name + (/pdf/i.test(mime) ? '.pdf' : '.jpg')),
    is_private: 1, content: base64, decode: true,
    attached_to_doctype: DOCTYPE, attached_to_name: name, attached_to_field: 'attachment',
  })
  if (!r.ok) log('attachToErp ' + name + ': ' + r.error)
  return r.ok
}

// OCR une pièce → crée un intake ERPNext (+ pièce versée). Dédup gérée en amont (seen-set).
async function ingestAttachment ({ mb, msgId, from, subject, date, att }) {
  const vision = require('./vision'); const gmail = require('./gmail')
  const isPdf = /pdf/i.test(att.mimeType) || /\.pdf$/i.test(att.filename || '')
  const mime = isPdf ? 'application/pdf' : (att.mimeType && att.mimeType.startsWith('image/') ? att.mimeType : 'image/jpeg')
  const base64 = await gmail.getAttachment(msgId, att.attachmentId, mb)
  let p = {}
  try { p = await vision.extractInvoice(base64, mime) } catch (e) { log('supplier OCR ' + att.filename + ': ' + e.message); p = { ocr_error: e.message } }
  const doc = {
    status: p.ocr_error ? 'Error' : 'New',
    vendor: p.vendor || null, invoice_number: p.invoice_number || null,
    invoice_date: p.date || null, due_date: p.due_date || null,
    currency: p.currency || 'CAD', subtotal: p.subtotal || 0, tax_gst: p.tax_gst || 0, tax_qst: p.tax_qst || 0, total: p.total || 0,
    email_from: from, email_subject: subject, email_date: date, gmail_msg_id: msgId,
    attachment_filename: att.filename || '', ocr_error: p.ocr_error || null,
    raw_ocr: JSON.stringify(p).slice(0, 100000),
  }
  const r = await erp.create(DOCTYPE, doc)
  if (!r.ok) { log('intake create échec: ' + r.error); return false }
  await attachToErp(r.name, att.filename, mime, base64).catch(() => {})
  return true
}

// Poll : courriels to:factures@ (dans cc@). Idempotent (seen par message).
async function pollInvoices () {
  const gmail = require('./gmail'); const mb = gmail.mailbox()
  const seen = loadSeenSet(SEEN); let added = 0
  let ids = []
  try { ids = await gmail.listMessages({ q: `newer_than:14d to:${INVOICE_TO()}`, max: 30, mb }) } catch (e) { log('pollInvoices list: ' + e.message); return { ok: false, error: e.message } }
  for (const { id } of ids.slice().reverse()) {
    if (seen.has(id)) continue
    try {
      const m = await gmail.getMessage(id, mb)
      for (const att of (m.attachments || []).filter(isOcrable)) { if (await ingestAttachment({ mb, msgId: id, from: m.from, subject: m.subject, date: m.date, att })) added++ }
      seen.add(id)
    } catch (e) { log('pollInvoices msg ' + id + ': ' + e.message) }
  }
  saveSeenSet(SEEN, seen)
  return { ok: true, scanned: ids.length, added }
}

// Crée un Purchase Invoice DRAFT (best-effort, jamais soumis). Échec → intake passe 'Error' + message ; saisie manuelle possible.
async function createDraft (name) {
  const c = await erp.get(DOCTYPE, name)
  if (!c || !c.name) return { ok: false, error: 'intake introuvable' }
  let supplier = c.supplier || null
  if (!supplier && c.vendor) {
    try { const sups = await erp.list('Supplier', { filters: [['supplier_name', 'like', '%' + c.vendor + '%']], fields: ['name'], limit: 1 }); if (sups && sups[0]) supplier = sups[0].name } catch (e) { /* */ }
  }
  const doc = {
    bill_no: c.invoice_number || undefined, bill_date: c.invoice_date || undefined, currency: c.currency || 'CAD',
    items: [{ item_name: ((c.vendor || 'Facture') + (c.invoice_number ? ' ' + c.invoice_number : '')).slice(0, 140), description: 'Facture fournisseur (OCR) — ' + (c.attachment_filename || name), qty: 1, rate: Number(c.total || c.subtotal || 0) || 0 }],
  }
  if (supplier) doc.supplier = supplier
  const pi = await erp.create('Purchase Invoice', doc)
  if (pi && pi.ok) { await erp.update(DOCTYPE, name, { status: 'Created', supplier: supplier || undefined, purchase_invoice: pi.name, ocr_error: null }); return { ok: true, name: pi.name, supplier_matched: !!supplier } }
  await erp.update(DOCTYPE, name, { status: 'Error', ocr_error: (pi && pi.error) || 'échec création' })
  return { ok: false, error: (pi && pi.error) || 'échec création', supplier_matched: !!supplier }
}

// Récupère le binaire d'un fichier privé ERPNext (pour l'affichage dans OPS), authentifié comme erpFetch.
function erpFileBinary (fileUrl) {
  return new Promise((resolve, reject) => {
    const u = new URL(cfg.ERP_URL + fileUrl)
    const req = http.request({ hostname: u.hostname, port: u.port || 8000, path: u.pathname + u.search, method: 'GET', headers: { Host: cfg.ERP_SITE, Authorization: 'token ' + cfg.ERP_TOKEN } }, (res) => {
      if (res.statusCode >= 400) { res.resume(); return reject(new Error('ERP file ' + res.statusCode)) }
      const chunks = []; res.on('data', c => chunks.push(c)); res.on('end', () => resolve({ buffer: Buffer.concat(chunks), contentType: res.headers['content-type'] || 'application/octet-stream' }))
    })
    req.on('error', reject); req.end()
  })
}

async function handle (req, res, method, p, url) {
  if (p === '/supplier-invoices' && method === 'GET') {
    const showAll = url && url.searchParams.get('all') === '1'
    const filters = showAll ? [] : [['status', '!=', 'Ignored']]
    const fields = ['name', 'status', 'vendor', 'supplier', 'invoice_number', 'invoice_date', 'due_date', 'currency', 'subtotal', 'tax_gst', 'tax_qst', 'total', 'attachment', 'attachment_filename', 'email_from', 'email_subject', 'email_date', 'ocr_error', 'purchase_invoice', 'creation']
    let invoices = []
    try { invoices = await erp.list(DOCTYPE, { filters, fields, limit: 200, orderBy: 'creation desc' }) } catch (e) { return json(res, 200, { invoices: [], error: e.message, invoice_to: INVOICE_TO() }) }
    return json(res, 200, { invoices, invoice_to: INVOICE_TO(), erp_base: (cfg.ERP_PUBLIC_URL || 'https://erp.gigafibre.ca') })
  }
  if (p === '/supplier-invoices/poll' && method === 'POST') return json(res, 200, await pollInvoices())
  const m = p.match(/^\/supplier-invoices\/([^/]+)\/(attachment|create|ignore)$/)
  if (m) {
    const name = decodeURIComponent(m[1]); const action = m[2]
    if (action === 'attachment' && method === 'GET') {
      try {
        const c = await erp.get(DOCTYPE, name)
        if (!c || !c.attachment) return json(res, 404, { error: 'pas de pièce' })
        const f = await erpFileBinary(c.attachment)
        res.writeHead(200, { 'Content-Type': f.contentType, 'Content-Disposition': 'inline; filename="' + (c.attachment_filename || 'piece') + '"' })
        return res.end(f.buffer)
      } catch (e) { return json(res, 404, { error: 'pièce introuvable: ' + e.message }) }
    }
    if (action === 'create' && method === 'POST') return json(res, 200, await createDraft(name))
    if (action === 'ignore' && method === 'POST') { const r = await erp.update(DOCTYPE, name, { status: 'Ignored' }); return json(res, r.ok ? 200 : 500, r.ok ? { ok: true } : { error: r.error }) }
  }
  return json(res, 404, { error: 'not found' })
}

let _timer = null
function start () {
  if (String(process.env.GMAIL_INGEST || '').toLowerCase() === 'off') return
  const ms = (Number(process.env.INVOICE_POLL_MIN) || 10) * 60000
  if (_timer) clearInterval(_timer)
  _timer = setInterval(() => pollInvoices().catch(e => log('pollInvoices: ' + e.message)), ms)
  log('Factures fournisseurs : poll ' + (ms / 60000) + ' min sur ' + require('./gmail').mailbox() + ' (to:' + INVOICE_TO() + ') → ERPNext ' + DOCTYPE)
  pollInvoices().catch(() => {})
}

module.exports = { handle, pollInvoices, createDraft, start }
