#!/usr/bin/env node
'use strict'
/**
 * setup_mailjet_webhook.js — Register the Hub's /campaigns/webhook URL with
 * Mailjet's Event API for every event type we care about.
 *
 * Mailjet's API uses ONE eventcallbackurl record PER event type. We want to
 * be notified about: sent, open, click, bounce, blocked, spam, unsub. So this
 * script idempotently registers (POST) or updates (PUT) one record per type.
 *
 * Auth: SMTP_USER + SMTP_PASS env vars (same creds work for the REST API on
 * Mailjet — they call them API_PUBLIC_KEY / API_PRIVATE_KEY but the values
 * are identical to the SMTP credentials).
 *
 * Usage:
 *   export SMTP_USER=<MJ_APIKEY_PUBLIC>
 *   export SMTP_PASS=<MJ_APIKEY_PRIVATE>
 *   node setup_mailjet_webhook.js --url https://msg.gigafibre.ca/campaigns/webhook
 *
 *   # Production-safe defaults:
 *   #   --is-backup false       primary (not backup) callback URL
 *   #   --group-events true     send events as a JSON array (recommended,
 *   #                          minimizes hub load — one POST per ~50 events
 *   #                          instead of one POST per event)
 *
 * To inspect / delete what's registered:
 *   node setup_mailjet_webhook.js --list
 *   node setup_mailjet_webhook.js --delete <id>
 */

const https = require('https')

const ALL_EVENTS  = ['sent', 'open', 'click', 'bounce', 'blocked', 'spam', 'unsub']
// Safe defaults: only events that aren't typically already claimed by other
// integrations (WP-Mail-SMTP on targo.ca currently owns sent/bounce/blocked
// — see `--list` output). open + click are the events the gift campaign
// actually needs for tracking; spam + unsub are nice-to-have signals.
const SAFE_EVENTS = ['open', 'click', 'spam', 'unsub']

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
}

function mjApi (method, urlPath, body, { user, pass }) {
  return new Promise((resolve, reject) => {
    const data = body ? JSON.stringify(body) : null
    const auth = Buffer.from(`${user}:${pass}`).toString('base64')
    const req = https.request({
      host: 'api.mailjet.com',
      path: '/v3/REST' + urlPath,
      method,
      headers: {
        'Authorization': 'Basic ' + auth,
        '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: chunks ? JSON.parse(chunks) : {} }) }
        catch (e) { resolve({ status: res.statusCode, body: chunks }) }
      })
    })
    req.on('error', reject)
    if (data) req.write(data)
    req.end()
  })
}

async function listCallbacks (creds) {
  const r = await mjApi('GET', '/eventcallbackurl?Limit=100', null, creds)
  if (r.status !== 200) throw new Error(`GET eventcallbackurl ${r.status}: ${JSON.stringify(r.body)}`)
  return r.body.Data || []
}

async function deleteCallback (id, creds) {
  const r = await mjApi('DELETE', `/eventcallbackurl/${id}`, null, creds)
  return r.status === 204 || r.status === 200
}

async function upsert (eventType, url, isBackup, creds, existing) {
  // existing array is the result of GET — find a matching record (same
  // EventType + IsBackup combination). Mailjet only allows ONE primary +
  // ONE backup URL per event, so this combination is the unique key.
  const match = existing.find(r => r.EventType === eventType && Boolean(r.IsBackup) === isBackup)
  const payload = { EventType: eventType, IsBackup: isBackup, Url: url, Status: 'alive', Version: 2 }
  if (match) {
    const r = await mjApi('PUT', `/eventcallbackurl/${match.ID}`, payload, creds)
    return { action: 'updated', id: match.ID, status: r.status, ok: r.status === 200 }
  } else {
    const r = await mjApi('POST', '/eventcallbackurl', payload, creds)
    const id = r.body.Data?.[0]?.ID
    return { action: 'created', id, status: r.status, ok: r.status === 201 || r.status === 200 }
  }
}

async function main () {
  const args = parseArgs(process.argv)
  const user = process.env.SMTP_USER || process.env.MJ_APIKEY_PUBLIC
  const pass = process.env.SMTP_PASS || process.env.MJ_APIKEY_PRIVATE
  if (!user || !pass) {
    console.error('Set SMTP_USER + SMTP_PASS (or MJ_APIKEY_PUBLIC + MJ_APIKEY_PRIVATE).')
    process.exit(1)
  }
  const creds = { user, pass }

  // --list — dump current config and exit
  if (args.list) {
    const callbacks = await listCallbacks(creds)
    console.log(`\n── Registered event callbacks: ${callbacks.length} ──`)
    for (const c of callbacks) {
      const flag = c.IsBackup ? '[BACKUP]' : '[PRIMARY]'
      console.log(`  ${flag} id=${c.ID}  event=${c.EventType.padEnd(10)}  status=${c.Status}  v${c.Version}  → ${c.Url}`)
    }
    process.exit(0)
  }

  // --delete <id>
  if (args.delete && args.delete !== true) {
    const ok = await deleteCallback(args.delete, creds)
    console.log(ok ? `  ✓ deleted callback ${args.delete}` : `  ✗ delete failed`)
    process.exit(ok ? 0 : 1)
  }

  const url = args.url
  if (!url || url === true) {
    console.error('Missing --url <callback-url>')
    console.error('Example: --url https://msg.gigafibre.ca/campaigns/webhook')
    process.exit(1)
  }
  if (!url.startsWith('https://')) {
    console.error('Mailjet requires HTTPS. Got:', url)
    process.exit(1)
  }
  const isBackup = args['is-backup'] === 'true' || args['is-backup'] === true

  // Resolve which events to configure
  let events
  if (args.all) {
    events = ALL_EVENTS
  } else if (args.events && args.events !== true) {
    events = args.events.split(',').map(e => e.trim()).filter(Boolean)
  } else {
    events = SAFE_EVENTS
  }

  const existing = await listCallbacks(creds)

  // Pre-flight: detect conflicts with existing PRIMARY records pointing
  // elsewhere. Refuse to overwrite unless --force-takeover is passed.
  const conflicts = events
    .map(ev => ({ ev, hit: existing.find(r => r.EventType === ev && Boolean(r.IsBackup) === isBackup) }))
    .filter(c => c.hit && c.hit.Url !== url)

  console.log(`\n── Mailjet Event API webhook setup ──`)
  console.log(`  callback URL:  ${url}`)
  console.log(`  type:          ${isBackup ? 'BACKUP' : 'PRIMARY'}`)
  console.log(`  events:        ${events.join(', ')}`)
  console.log(`  existing:      ${existing.length} records on the account`)

  if (conflicts.length && !args['force-takeover']) {
    console.log(`\n  ⚠ Conflicts detected — these events already point elsewhere:`)
    for (const c of conflicts) {
      console.log(`    • ${c.ev.padEnd(10)} → ${c.hit.Url}  (id=${c.hit.ID})`)
    }
    console.log(`\n  Refusing to overwrite without --force-takeover. Either:`)
    console.log(`    • Exclude the conflicting events:  --events open,click`)
    console.log(`    • Or override the existing config: --force-takeover`)
    process.exit(1)
  }
  console.log()

  let okCount = 0
  for (const ev of events) {
    process.stdout.write(`  ${ev.padEnd(10)} ... `)
    const r = await upsert(ev, url, isBackup, creds, existing)
    if (r.ok) {
      okCount++
      console.log(`✓ ${r.action} (id=${r.id})`)
    } else {
      console.log(`✗ status=${r.status}`)
    }
  }

  console.log(`\n  ${okCount}/${events.length} events configured.`)
  console.log(`\n  Verify with:  node setup_mailjet_webhook.js --list`)
  console.log(`  Mailjet dashboard: Account Settings → REST API → Event tracking\n`)
}

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