/**
 * api/campaigns.js — Client for Hub /campaigns endpoints.
 *
 * Mirrors services/targo-hub/lib/campaigns.js. All gift-campaign requests
 * go through the Hub which handles ERPNext auth + Mailjet send + SSE
 * progress broadcast.
 *
 * Functions:
 *   parseCsvs({ map_csv, giftbit_csv, multi })   → preview matched send list
 *   createCampaign({ name, params, recipients }) → save + return id
 *   listCampaigns()                              → summaries
 *   getCampaign(id)                              → full detail
 *   updateCampaign(id, patch)                    → edit recipients/params
 *   sendCampaign(id)                             → fire background worker
 *   campaignSseUrl(id)                           → SSE URL for live updates
 */

import { HUB_URL } from 'src/config/hub'

async function hubFetch (path, { method = 'GET', body } = {}) {
  const opts = { method, headers: { 'Content-Type': 'application/json' } }
  if (body) opts.body = JSON.stringify(body)
  const res = await fetch(`${HUB_URL}${path}`, opts)
  const text = await res.text()
  let data
  try { data = text ? JSON.parse(text) : {} }
  catch { throw new Error(`Invalid JSON from ${path}: ${text.slice(0, 200)}`) }
  if (!res.ok) {
    const msg = data.error || `HTTP ${res.status}`
    const err = new Error(msg)
    err.status = res.status
    throw err
  }
  return data
}

export function parseCsvs ({ map_csv, giftbit_csv, multi = 'first' }) {
  return hubFetch('/campaigns/parse', {
    method: 'POST',
    body: { map_csv, giftbit_csv, multi },
  })
}

export function createCampaign ({ name, params, recipients }) {
  return hubFetch('/campaigns', {
    method: 'POST',
    body: { name, params, recipients },
  })
}

export function listCampaigns () {
  return hubFetch('/campaigns').then(r => r.campaigns || [])
}

export function getCampaign (id) {
  return hubFetch(`/campaigns/${encodeURIComponent(id)}`)
}

export function updateCampaign (id, patch) {
  return hubFetch(`/campaigns/${encodeURIComponent(id)}`, {
    method: 'PATCH',
    body: patch,
  })
}

export function sendCampaign (id) {
  return hubFetch(`/campaigns/${encodeURIComponent(id)}/send`, {
    method: 'POST',
  })
}

// Duplicate a campaign as a fresh draft (id "_2", recipients reset to pending).
// Returns the new draft campaign so the caller can open it for review/send.
export function cloneCampaign (id) {
  return hubFetch(`/campaigns/${encodeURIComponent(id)}/clone`, {
    method: 'POST',
  })
}

// Permanent deletion — removes the JSON on the hub. Used for clearing
// test campaigns from the list. Giftbit shortlinks are unaffected.
export function deleteCampaign (id) {
  return hubFetch(`/campaigns/${encodeURIComponent(id)}`, {
    method: 'DELETE',
  })
}

// Inventory of every wrapper token across all campaigns, with status
// (active / expired / revoked / redeemed / pending). Used by the
// gifts inventory page to surface reassignable Giftbit shortlinks.
export function listGifts () {
  return hubFetch('/campaigns/gifts').then(r => r.gifts || [])
}

// Pool de liens-cadeaux récupérés (jamais cliqués) prêts à réallouer à d'autres clients.
export function getGiftPool () {
  return hubFetch('/campaigns/gifts/pool')
}

// Récupère les liens non cliqués d'une campagne vers le pool (dry_run par défaut = aperçu).
export function reclaimGifts ({ campaign_id, dry_run = true, revoke_live = true }) {
  return hubFetch('/campaigns/gifts/reclaim', { method: 'POST', body: { campaign_id, dry_run, revoke_live } })
}

// Envoi unitaire d'une récompense depuis le pool vers un client (dry_run pour aperçu).
export function rewardSend (body) {
  return hubFetch('/campaigns/reward-send', { method: 'POST', body })
}

// Récompenses déjà préparées/envoyées à un client (pour la fiche : voir / copier le lien).
export function rewardByCustomer (customer_id, email) {
  const q = customer_id ? 'customer_id=' + encodeURIComponent(customer_id) : 'email=' + encodeURIComponent(email || '')
  return hubFetch('/campaigns/reward/by-customer?' + q)
}

// Supprime un brouillon / révoque une récompense non cliquée (remet le lien en réserve ; refusé si déjà ouverte).
export function revokeRewardByToken (gift_token) {
  return hubFetch('/campaigns/reward/revoke', { method: 'POST', body: { gift_token } })
}

// Révoque une récompense déjà envoyée + remet le lien Giftbit au pool (refusé si déjà cliqué/redirigé).
export function revokeReward (gift_url) {
  return hubFetch('/campaigns/gifts/revoke-reward', { method: 'POST', body: { gift_url } })
}

// Kill switch — manually expire a single recipient's wrapper token so
// the underlying Giftbit URL becomes reassignable before the natural
// expiry date.
export function revokeGift (campaignId, rowIndex) {
  return hubFetch(
    `/campaigns/${encodeURIComponent(campaignId)}/recipients/${rowIndex}/revoke`,
    { method: 'POST' },
  )
}

// Re-attempt a single failed recipient — resets status pending and
// fires the worker. Used for one-off failures the auto-retry didn't
// recover (rare transient Mailjet socket closes, etc.).
export function retryRecipient (campaignId, rowIndex) {
  return hubFetch(
    `/campaigns/${encodeURIComponent(campaignId)}/recipients/${rowIndex}/retry`,
    { method: 'POST' },
  )
}

// Create a brand-new draft campaign targeted at the parent's non-clicked
// recipients. Uses gift-email-reminder-* templates + an urgency subject.
// The new campaign is returned but NOT auto-sent — the operator reviews
// and clicks Lancer l'envoi when ready.
export function createReminderCampaign (parentCampaignId) {
  return hubFetch(
    `/campaigns/${encodeURIComponent(parentCampaignId)}/reminder`,
    { method: 'POST' },
  )
}

// Build the URL the browser hits to download the per-recipient CSV report
// (giftbit shortlink ↔ email ↔ name ↔ status). The hub serves it with the
// proper Content-Disposition so an <a download> click triggers a save.
export function campaignReportCsvUrl (id) {
  return `${HUB_URL}/campaigns/${encodeURIComponent(id)}/report.csv`
}

// ── Image assets (self-hosted on the hub, for GrapesJS asset manager) ───────

export function listAssets () {
  return hubFetch('/campaigns/assets').then(r => r.assets || [])
}

// Upload a File / Blob from the browser via base64-encoded JSON. Bypasses
// multipart parsing on the hub side (zero new deps) at the cost of ~33%
// payload overhead. Acceptable for the ≤5 MB images we permit.
export async function uploadAsset (file) {
  const dataUrl = await new Promise((resolve, reject) => {
    const r = new FileReader()
    r.onload = () => resolve(r.result)
    r.onerror = () => reject(new Error('FileReader failed'))
    r.readAsDataURL(file)
  })
  return hubFetch('/campaigns/assets/upload', {
    method: 'POST',
    body: { name: file.name, data: dataUrl },
  })
}

export function deleteAsset (filename) {
  return hubFetch(`/campaigns/assets/${encodeURIComponent(filename)}`, {
    method: 'DELETE',
  })
}

// ── Template editing (used by the GrapesJS editor page) ─────────────────────

export function listTemplates () {
  return hubFetch('/campaigns/templates').then(r => r.templates || [])
}

export function getTemplate (name) {
  return hubFetch(`/campaigns/templates/${encodeURIComponent(name)}`)
}

// saveTemplate(name, content, opts) — content is HTML by default.
// Optional opts.design = Unlayer design JSON (persisted alongside HTML so the
// editor can re-load the visual state on next open).
// Legacy opts.format = 'mjml' still supported for older callers (sends mjml).
export function saveTemplate (name, content, { format = 'html', design = null } = {}) {
  const body = format === 'mjml' ? { mjml: content } : { html: content }
  if (design) body.design = design
  return hubFetch(`/campaigns/templates/${encodeURIComponent(name)}`, {
    method: 'PUT',
    body,
  })
}

export function previewTemplate (name, { html, vars } = {}) {
  return hubFetch(`/campaigns/templates/${encodeURIComponent(name)}/preview`, {
    method: 'POST',
    body: { html, vars },
  })
}

// Translate the source template to a target language via Gemini.
// targetName must match the source's prefix (e.g. gift-email-fr → gift-email-en).
// override=true required if the target already exists.
export function translateTemplate (srcName, targetName, { override = false } = {}) {
  return hubFetch(
    `/campaigns/templates/${encodeURIComponent(srcName)}/translate-to/${encodeURIComponent(targetName)}`,
    { method: 'POST', body: { override } },
  )
}

// Send ONE rendered email to a specific address for visual QA.
// Pass { to, vars, from?, subject? } — defaults filled in server-side.
export function testSendTemplate (name, { to, vars, from, subject, via } = {}) {
  return hubFetch(`/campaigns/templates/${encodeURIComponent(name)}/test-send`, {
    method: 'POST',
    body: { to, vars, from, subject, via },
  })
}

/**
 * Returns the URL for the SSE channel of one campaign. The Hub broadcasts on
 * topic `campaign:<id>` so we subscribe to that single topic. Use with:
 *   const es = new EventSource(campaignSseUrl(id))
 *   es.addEventListener('recipient-update', ev => { ... })
 *   es.addEventListener('campaign-done',    ev => { ... })
 */
export function campaignSseUrl (id) {
  return `${HUB_URL}/sse?topics=campaign:${encodeURIComponent(id)}`
}
