#!/usr/bin/env node
'use strict'
/**
 * create_giftbit_campaign.js — Create a Giftbit campaign via API and pull
 * back the per-recipient gift URLs as a CSV ready for send_gift_campaign.js.
 *
 * Giftbit's default delivery_type sends emails THEMSELVES (in English).
 * We don't want that — we want the gift URLs returned to us so we can
 * send French personalized emails through our own Mailjet. This script
 * uses delivery_type=SHORTLINK which is the API value for "give me the
 * links, I'll deliver them".
 *
 * Usage:
 *   GIFTBIT_TOKEN=<your_token> \
 *   node create_giftbit_campaign.js \
 *     --contacts ./giftbit-contacts-A-first-email.csv \
 *     --amount-cents 5000 \
 *     --brand-codes amazonca,walmart,timhortonsca \
 *     --expiry 2026-12-31 \
 *     --subject "Cadeau Gigafibre" \
 *     --message "Merci d'être client" \
 *     [--sandbox]              ← uses api-testbed.giftbit.com + louis@targo.ca for all
 *     [--id my-campaign-2026-q4]
 *
 * Output: giftbit-gifts-<campaign-id>.csv with one row per recipient:
 *   firstname,lastname,email,gift_url,giftbit_uuid,gift_value_cents
 *
 * Feed this CSV (+ the contacts CSV) to send_gift_campaign.js for the FR
 * email blast through Mailjet.
 */

const fs   = require('fs')
const path = require('path')
const https = require('https')

// ── CLI parse ──────────────────────────────────────────────────────────────
function parseArgs (argv) {
  const out = {}
  for (let i = 2; i < argv.length; i++) {
    const a = argv[i]
    if (a.startsWith('--')) {
      const k = a.slice(2); const next = argv[i + 1]
      if (!next || next.startsWith('--')) out[k] = true
      else { out[k] = next; i++ }
    }
  }
  return out
}
const args = parseArgs(process.argv)
const TOKEN = process.env.GIFTBIT_TOKEN
if (!TOKEN) { console.error('Set GIFTBIT_TOKEN env var.'); process.exit(1) }

const SANDBOX = !!args.sandbox
const HOST = SANDBOX ? 'api-testbed.giftbit.com' : 'api.giftbit.com'
const ROOT = '/papi/v1'

const REQUIRED = ['contacts', 'amount-cents', 'brand-codes', 'expiry', 'subject', 'message']
for (const k of REQUIRED) {
  if (!args[k]) { console.error(`Missing --${k}`); process.exit(1) }
}

const CAMPAIGN_ID = args.id || ('gigafibre-' + new Date().toISOString().slice(0, 13).replace(/[-:T]/g, ''))

// ── CSV parser (same as send_gift_campaign.js, kept self-contained) ───────
function parseCsv (text) {
  const sample = text.split(/\r?\n/, 1)[0] || ''
  const delim = sample.includes(',') ? ',' : (sample.includes('\t') ? '\t' : '|')
  const rows = []; let row = [], field = '', inQ = false
  for (let i = 0; i < text.length; i++) {
    const c = text[i]
    if (inQ) {
      if (c === '"' && text[i + 1] === '"') { field += '"'; i++ }
      else if (c === '"') inQ = false
      else field += c
    } else {
      if (c === '"') inQ = true
      else if (c === delim) { row.push(field); field = '' }
      else if (c === '\n' || c === '\r') {
        if (field !== '' || row.length) { row.push(field); rows.push(row); row = []; field = '' }
        if (c === '\r' && text[i + 1] === '\n') i++
      } else field += c
    }
  }
  if (field !== '' || row.length) { row.push(field); rows.push(row) }
  if (!rows.length) return []
  const header = rows[0].map(h => h.trim())
  return rows.slice(1).filter(r => r.some(c => c !== '')).map(r => {
    const o = {}; header.forEach((h, i) => { o[h] = (r[i] || '').trim() }); return o
  })
}

// ── HTTPS helper ───────────────────────────────────────────────────────────
function apiCall (method, urlPath, body) {
  return new Promise((resolve, reject) => {
    const data = body ? JSON.stringify(body) : null
    const req = https.request({
      host: HOST, path: ROOT + urlPath, method,
      headers: {
        'Authorization': 'Bearer ' + TOKEN,
        'Accept': 'application/json',
        ...(data ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } : {}),
      },
    }, res => {
      let chunks = ''
      res.on('data', c => { chunks += c })
      res.on('end', () => {
        try { resolve({ status: res.statusCode, body: JSON.parse(chunks) }) }
        catch (e) { resolve({ status: res.statusCode, body: chunks }) }
      })
    })
    req.on('error', reject)
    if (data) req.write(data)
    req.end()
  })
}

function csvCell (s) {
  s = String(s == null ? '' : s)
  return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s
}

// ── Main ───────────────────────────────────────────────────────────────────
async function main () {
  console.log(`\n── Giftbit campaign create ${SANDBOX ? '(SANDBOX)' : '(PRODUCTION)'} ──`)
  console.log(`  host:           ${HOST}`)
  console.log(`  id:             ${CAMPAIGN_ID}`)
  console.log(`  amount:         ${args['amount-cents']} cents ($${(+args['amount-cents'] / 100).toFixed(2)})`)
  console.log(`  brands:         ${args['brand-codes']}`)
  console.log(`  expiry:         ${args.expiry}`)
  console.log(`  contacts:       ${args.contacts}`)

  // Verify auth first — fail fast if token is bad
  const ping = await apiCall('GET', '/ping')
  if (ping.status !== 200) {
    console.error('  ✗ Auth failed:', ping.body); process.exit(1)
  }
  console.log(`  ✓ auth ok — ${ping.body.displayname} (${ping.body.username})`)

  // Load and prepare contacts
  const contactsRaw = parseCsv(fs.readFileSync(args.contacts, 'utf8'))
  if (!contactsRaw.length) { console.error('Empty contacts CSV'); process.exit(1) }

  // SANDBOX SAFETY: route every email to louis@targo.ca so we don't
  // accidentally hit real customer inboxes with non-redeemable test gifts.
  const contacts = contactsRaw.map((c, idx) => ({
    firstname: c.firstname || '',
    lastname:  c.lastname  || '',
    email:     SANDBOX ? 'louis@targo.ca' : c.email,
    // Pass our internal identifier so the response can be joined back to
    // the customer record in ERPNext. We use `id` because that's the
    // Giftbit-standard field for contact reference.
    id:        c.account_id || c.email || `row-${idx + 1}`,
  }))
  console.log(`  ${contacts.length} contacts loaded${SANDBOX ? ' — ALL routed to louis@targo.ca' : ''}`)

  // Build campaign payload.
  // delivery_type=SHORTLINK is critical: it tells Giftbit "create the gifts
  // but DON'T email the recipients — give me back the URLs so I can deliver
  // them myself". Without this (default = GIFTBIT_EMAIL) Giftbit sends
  // their English template emails immediately, defeating the whole purpose
  // of routing through our French Mailjet template.
  const payload = {
    id: CAMPAIGN_ID,
    subject: args.subject,
    message: args.message,
    expiry: args.expiry,
    price_in_cents: parseInt(args['amount-cents'], 10),
    brand_codes: args['brand-codes'].split(',').map(s => s.trim()),
    contacts,
    delivery_type: 'SHORTLINK',
  }

  const create = await apiCall('POST', '/campaign', payload)
  if (create.status !== 200) {
    console.error('  ✗ create failed:', JSON.stringify(create.body, null, 2)); process.exit(1)
  }
  console.log(`  ✓ campaign created: ${create.body.campaign?.uuid || CAMPAIGN_ID}`)

  // Wait for gift shortlinks to be generated (Giftbit creates them
  // asynchronously when delivery_type=SHORTLINK). Each /gifts row has
  // a `shortlink` field (http://gtbt.co/XXX) once delivery_status
  // transitions from QUEUED → LINKCREATED.
  const campaignUuid = create.body.campaign?.uuid || CAMPAIGN_ID
  console.log(`  → polling /gifts?campaign_uuid=${campaignUuid} for shortlinks…`)
  let gifts = []
  for (let attempt = 0; attempt < 20; attempt++) {
    await new Promise(r => setTimeout(r, 2000))
    const r = await apiCall('GET', `/gifts?campaign_uuid=${encodeURIComponent(campaignUuid)}&limit=500`)
    if (r.status === 200) {
      gifts = (r.body.gifts || []).filter(g => g.shortlink)
      if (gifts.length >= contacts.length) break
      process.stdout.write(`    ${gifts.length}/${contacts.length}… `)
    }
  }
  console.log(`\n  ✓ ${gifts.length} gifts with shortlinks`)

  if (!gifts.length) {
    console.error('  no shortlinks — campaign may still be processing.')
    console.error('  retry: curl -H "Authorization: Bearer $GIFTBIT_TOKEN" \\')
    console.error(`         ${SANDBOX ? 'https://api-testbed.giftbit.com' : 'https://api.giftbit.com'}/papi/v1/gifts?campaign_uuid=${campaignUuid}`)
    process.exit(1)
  }

  // Match returned gifts to original contacts via recipient_email +
  // ordering. Since Giftbit doesn't echo back our contact `id` field,
  // we rely on the sequence Giftbit processed them in — usually
  // matches the order we POSTed contacts. For production with unique
  // emails per contact, recipient_email is a clean join key.
  const out = path.resolve(`giftbit-gifts-${CAMPAIGN_ID}.csv`)
  const w = fs.createWriteStream(out, { encoding: 'utf8' })
  w.write('firstname,lastname,email,gift_url,giftbit_uuid,gift_value_cents,internal_id\n')
  const sortedGifts = gifts.sort((a, b) => (a.created_date || '').localeCompare(b.created_date || ''))
  for (let i = 0; i < contacts.length && i < sortedGifts.length; i++) {
    const g = sortedGifts[i]; const c = contacts[i]
    w.write([
      csvCell(c.firstname), csvCell(c.lastname), csvCell(c.email),
      csvCell(g.shortlink),
      csvCell(g.uuid),
      csvCell(g.price_in_cents),
      csvCell(c.id),
    ].join(',') + '\n')
  }
  w.end()
  console.log(`\n  → ${out}`)
  console.log(`\nNext: pass this CSV to send_gift_campaign.js to blast the FR emails.`)
}

main().catch(e => { console.error('Fatal:', e); process.exit(1) })
