'use strict'
/**
 * Évaluation client par étoiles (review-gating) — insérable dans un courriel via le marqueur {{rating}}.
 *
 *  - Bloc 5 étoiles : chaque étoile = un lien <a> (les courriels n'exécutent pas de JS).
 *    Survol = remplit les étoiles à GAUCHE (truc CSS reverse-order + direction:rtl + `a:hover ~ a`).
 *    Marche dans l'aperçu du composeur Ops + les clients qui gardent <style>/:hover ; dégrade en étoiles
 *    cliquables ailleurs (le clic redirige toujours).
 *  - Clic : GET /rate?t=<token>&s=<1-5> → enregistre + redirige.
 *      s == 5  → page d'avis Google (GOOGLE_REVIEW_URL)
 *      s  < 5  → page interne « merci + qu'est-ce qu'on pourrait améliorer ? » (capte le mécontentement EN PRIVÉ).
 *  - Le marqueur {{rating}} est remplacé à l'ENVOI (côté hub) par le bloc, avec un token par destinataire.
 */
const crypto = require('crypto')
const fs = require('fs')
const path = require('path')
const cfg = require('./config')
const { log, json, parseBody, readJsonFile, writeJsonFile, lookupCustomersByEmail } = require('./helpers')
const sse = require('./sse')

const FILE = '/app/data/ratings.json'
const TPL_DIR = path.join(__dirname, '..', 'templates')
const pub = () => cfg.HUB_PUBLIC_URL || 'https://msg.gigafibre.ca'
const googleUrl = () => cfg.GOOGLE_REVIEW_URL || 'https://search.google.com/local/writereview?placeid='

function load () { const d = readJsonFile(FILE, {}); return (d && typeof d === 'object' && !Array.isArray(d)) ? d : {} }
function save (m) { writeJsonFile(FILE, m) }
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]))

// Crée un jeton d'évaluation (1 par destinataire) → mappe vers {customer, conv, email}.
function newRatingToken ({ customer = '', conv = '', email = '', name = '', lang = '' } = {}) {
  const token = crypto.randomBytes(9).toString('base64url')
  const m = load()
  m[token] = { customer, conv, email, name, lang: normLang(lang), created: new Date().toISOString(), stars: null, stars_ts: null, comment: null, comment_ts: null }
  save(m)
  return token
}

// Bloc « étoiles cliquables » compatible courriel (technique lang-hack + entités Unicode &#9733;/&#9734; — aucune police externe).
// Sélecteur d'évaluation COURRIEL : 5 LIGNES (5★ en haut → 1★ en bas), chaque ligne = un choix cliquable complet
// (pastille + N étoiles colorées + (5-N) grises). Style Gigafibre : 5★ = vert marque #00C853, dégradé jusqu'au rouge.
// Plus lisible/robuste que le survol (chaque ligne statique + explicite, AUCUNE animation requise — demande user).
// Couleur de fond sur le <td> (rendu fiable Outlook) + lien sur toute la pastille/les étoiles → /rate?t=&s=N.
function ratingBlock (token) {
  const base = `${pub()}/rate?t=${encodeURIComponent(token)}`
  const COLOR = { 5: '#00C853', 4: '#7CB342', 3: '#FBC02D', 2: '#FB8C00', 1: '#E53935' } // dégradé vert→rouge (5★ = vert Gigafibre)
  const EMPTY = '#D7DEE3'
  const SF = "font-family:'Segoe UI Symbol','Apple Symbols','Noto Sans Symbols','Arial Unicode MS',Arial,sans-serif"
  const row = (n) => {
    const href = `${base}&s=${n}`
    const title = `${n} étoile${n > 1 ? 's' : ''} sur 5`
    const dot = `<td width="22" style="padding:0 10px 0 0"><a href="${href}" target="_blank" rel="noopener" title="${title}" style="display:block;width:15px;height:15px;border:2px solid #c4cdd5;border-radius:50%;font-size:1px;line-height:1px;text-decoration:none">&nbsp;</a></td>`
    let sq = ''
    for (let k = 1; k <= 5; k++) {
      const bg = k <= n ? COLOR[n] : EMPTY
      sq += `<td bgcolor="${bg}" width="40" height="40" align="center" valign="middle" style="background:${bg};border-radius:8px"><a href="${href}" target="_blank" rel="noopener" title="${title}" style="display:block;width:40px;height:40px;line-height:40px;text-align:center;color:#ffffff;font-size:22px;text-decoration:none;${SF}">&#9733;</a></td>`
    }
    return `<tr>${dot}${sq}</tr>`
  }
  const rows = [5, 4, 3, 2, 1].map(row).join('')
  return `<table role="presentation" cellpadding="0" cellspacing="4" border="0" style="border-collapse:separate;margin:6px auto"><tbody>${rows}</tbody></table>`
}

// Remplace le marqueur {{rating}} / {{etoiles}} dans le HTML sortant par un bloc tokenisé pour CE destinataire.
async function expandRatingMarker (html, conv = {}) {
  const s = String(html || '')
  if (!/\{\{\s*(rating|etoiles|évaluation|evaluation)\s*\}\}/i.test(s)) return s
  // Une demande d'évaluation s'adresse à un client CONNU → on garantit le lien à la fiche.
  // Si la conversation n'est pas liée (conv.customer vide), on résout la fiche par COURRIEL.
  let customer = conv.customer || ''
  let name = conv.customerName || ''
  if (!customer && conv.email) {
    try { const r = await lookupCustomersByEmail(conv.email, 1); if (r && r[0]) { customer = r[0].name; if (!name) name = r[0].customer_name || '' } } catch (e) { /* lookup best-effort */ }
  }
  const token = newRatingToken({ customer, conv: conv.token || '', email: conv.email || '', name })
  return s.replace(/\{\{\s*(rating|etoiles|évaluation|evaluation)\s*\}\}/gi, ratingBlock(token))
}

// Page brandée Gigafibre (vert #00C853, encre #1B2E24, Plus Jakarta Sans) — même esprit que gigafibre.ca.
function shell (title, bodyHtml) {
  return `<!doctype html><html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>${esc(title)}</title>
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
:root{--g:#00C853;--ink:#1B2E24;--muted:#64748B;--line:#e6ebe8}
*{box-sizing:border-box}
body{margin:0;font-family:'Plus Jakarta Sans',-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:var(--ink);background:linear-gradient(165deg,#E6F9EE 0%,#F5FAF7 40%,#ffffff 100%);min-height:100vh}
.wrap{max-width:560px;margin:0 auto;padding:7vh 20px 40px}
.brand{display:flex;align-items:center;gap:9px;font-weight:800;font-size:1.4rem;letter-spacing:-.6px;margin-bottom:26px}
.brand .dot{width:12px;height:12px;border-radius:50%;background:var(--g);box-shadow:0 0 0 4px rgba(0,200,83,.18)}
.brand b{color:var(--g)}
.card{background:#fff;border:1px solid #eef2f0;border-radius:22px;box-shadow:0 18px 50px rgba(27,46,36,.10);padding:34px 30px}
h1{font-size:1.5rem;font-weight:800;letter-spacing:-.5px;margin:0 0 10px}
p{color:var(--muted);line-height:1.6;font-size:1rem;margin:0 0 8px}
.stars{font-size:2.1rem;letter-spacing:5px;color:#f5b50a;margin:2px 0 16px}
textarea{width:100%;border:1.5px solid var(--line);border-radius:14px;padding:14px 16px;font:inherit;font-size:1rem;min-height:130px;resize:vertical;background:#fcfdfc;transition:border-color .15s,box-shadow .15s}
textarea:focus{outline:none;border-color:var(--g);box-shadow:0 0 0 4px rgba(0,200,83,.15)}
button{margin-top:16px;background:var(--g);color:#06351d;border:0;border-radius:14px;padding:14px 26px;font:inherit;font-weight:700;font-size:1rem;cursor:pointer;box-shadow:0 8px 20px rgba(0,200,83,.28);transition:filter .15s,transform .08s}
button:hover{filter:brightness(1.05)}button:active{transform:translateY(1px)}
.ok{color:var(--g);font-weight:700;margin-top:16px;font-size:1.05rem}
.foot{text-align:center;color:#9aa7a0;font-size:.8rem;margin-top:22px}
</style></head>
<body><div class="wrap">
<div class="brand"><img src="https://msg.gigafibre.ca/campaigns/assets/6a2bcb6057d9881c08304a5d2fea8723e2a15b05061fbbe3590bd4a0ee714477.png" alt="TARGO" style="height:34px;width:auto;display:block" /></div>
<div class="card">${bodyHtml}</div>
<div class="foot">TARGO — merci pour votre confiance</div>
</div></body></html>`
}

function feedbackPage (token, stars) {
  const filled = '★'.repeat(Math.max(0, stars)) + '☆'.repeat(Math.max(0, 5 - stars))
  return shell('Merci pour votre évaluation', `
    <div class="stars">${filled}</div>
    <h1>Merci pour votre évaluation&nbsp;!</h1>
    <p>On vise toujours le 5&nbsp;étoiles. En quelques mots, qu'est-ce qui n'a pas été à la hauteur — ou comment pourrait-on faire mieux&nbsp;? Votre réponse va directement à notre équipe.</p>
    <form id="rf" onsubmit="return sendComment(event)">
      <textarea id="rc" name="comment" placeholder="Votre commentaire (optionnel, mais très apprécié)…"></textarea>
      <button type="submit">Envoyer mon commentaire</button>
    </form>
    <div id="done" class="ok" style="display:none">✓ Merci&nbsp;! C'est transmis à notre équipe — on vous revient au besoin.</div>
    <script>
      function sendComment(e){e.preventDefault();var f=document.getElementById('rf');var c=(f.comment.value||'').trim();if(!c){f.comment.focus();return false;}var b=f.querySelector('button');b.disabled=true;fetch('/rate/comment',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token:${JSON.stringify(token)},comment:c})}).then(function(){f.style.display='none';document.getElementById('done').style.display='block';}).catch(function(){b.disabled=false;});return false;}
    </script>`)
}

// ── Invitation d'évaluation bilingue (FR/EN) : renvoi courriel/SMS + page d'étoiles autonome (lien SMS) ──
// Invitation TUTOIEMENT (« tu ») — plus chaleureux/efficace que le vouvoiement.
const INVITE = {
  fr: { subject: 'Ton avis compte pour nous', title: 'Ton avis compte pour nous', lead: 'Comment évaluerais-tu ton expérience avec TARGO ?',
    greet: (n) => n ? `Bonjour ${n},` : 'Bonjour,',
    intro: "Merci de faire confiance à TARGO. Si tu as quelques secondes pour nous évaluer, ce serait vraiment apprécié :",
    outro: "C'est grâce à ta rétroaction qu'on peut toujours mieux te servir. Si quelque chose ne va pas, réponds simplement à ce courriel — on est là pour t'aider.",
    foot: 'TARGO — merci pour ta confiance',
    sms: "Merci d'avoir choisi TARGO ! Évalue ton expérience en quelques secondes : " },
  en: { subject: 'Your feedback matters to us', title: 'Your feedback matters', lead: 'How would you rate your experience with TARGO?',
    greet: (n) => n ? `Hi ${n},` : 'Hi,',
    intro: 'Thanks for choosing TARGO. If you have a few seconds to rate us, it would mean a lot:',
    outro: "Your feedback helps us serve you better. If something went wrong, just reply to this email — we're here to help.",
    foot: 'TARGO — thank you for your trust',
    sms: 'Thanks for choosing TARGO! Rate your experience in seconds: ' },
}
const INVITE_LOGO = 'https://msg.gigafibre.ca/campaigns/assets/6a2bcb6057d9881c08304a5d2fea8723e2a15b05061fbbe3590bd4a0ee714477.png'
function normLang (l) { return /^en/i.test(String(l || '')) ? 'en' : 'fr' }
function firstName (n) { return String(n || '').trim().split(/\s+/)[0] || '' }
function inviteSubject (lang) { return INVITE[normLang(lang)].subject }
// Courriel d'invitation MIS EN PAGE (même charte que le transactionnel d'excuse : carte blanche 600px + logo + pied),
// rehaussé d'un filet vert TARGO et des étoiles dans un encart vert. `stars` = ratingBlock(token) OU le marqueur {{rating}}.
function inviteEmailDesign (lang, name, stars) {
  const t = INVITE[normLang(lang)]
  return `<!doctype html><html lang="${normLang(lang)}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>`
    + `<body style="margin:0;background:#f4f6f8;font-family:-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:#1e293b">`
    + `<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td align="center" style="padding:40px 16px 28px">`
    + `<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px;background:#ffffff;border-radius:14px;overflow:hidden;border:1px solid #e2e8f0">`
    + `<tr><td style="background:#ffffff;padding:24px 28px 14px"><img src="${INVITE_LOGO}" alt="TARGO" width="132" style="width:132px;max-width:132px;height:auto;display:block;border:0"></td></tr>`
    + `<tr><td style="height:4px;background:#00C853;font-size:0;line-height:0;mso-line-height-rule:exactly">&nbsp;</td></tr>`
    + `<tr><td style="padding:28px 30px 4px"><h1 style="margin:0 0 10px;font-size:21px;font-weight:800;color:#0f172a">${esc(t.title)}</h1>`
    + `<p style="margin:0 0 4px;font-size:15px;line-height:1.6;color:#475569">${esc(t.greet(firstName(name)))}</p>`
    + `<p style="margin:0;font-size:15px;line-height:1.6;color:#475569">${esc(t.intro)}</p></td></tr>`
    + `<tr><td style="padding:14px 24px 6px"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#E6F9EE;border:1px solid #b6f0cf;border-radius:12px"><tr><td align="center" style="padding:14px 8px">${stars}</td></tr></table></td></tr>`
    + `<tr><td style="padding:8px 30px 26px"><p style="margin:0;font-size:14px;line-height:1.6;color:#64748b">${esc(t.outro)}</p></td></tr>`
    + `<tr><td style="background:#f8fafc;padding:16px 30px;border-top:1px solid #eef2f7;font-size:12px;color:#94a3b8">${esc(t.foot)}</td></tr>`
    + `</table></td></tr></table></body></html>`
}
// Modèle ÉDITABLE DANS UNLAYER : templates/transactional-rating-invite-<lang>.html (envoyé tel quel),
// avec marqueurs {{firstname}} (prénom du destinataire) + {{rating}} (bloc étoiles tokenisé à l'envoi).
// Repli sur le design codé en dur (inviteEmailDesign) si le fichier est absent/illisible.
// PAS de cache : une modif sauvegardée dans Unlayer (qui réécrit le .html) prend effet sans redémarrer le hub.
function loadInviteTpl (lang) {
  try { return fs.readFileSync(path.join(TPL_DIR, `transactional-rating-invite-${normLang(lang)}.html`), 'utf8') } catch { return null }
}
function mergeName (html, name) {
  const fn = esc(firstName(name))
  return String(html).replace(/\s*\{\{\s*firstname\s*\}\}/gi, fn ? ' ' + fn : '')
}
function inviteEmailHtml (token, lang, name) {
  const tpl = loadInviteTpl(lang)
  if (!tpl) return inviteEmailDesign(lang, name, ratingBlock(token))
  return mergeName(tpl, name).replace(/\{\{\s*rating\s*\}\}/gi, ratingBlock(token))
}
function inviteEmailMarkerHtml (lang, name) {
  const tpl = loadInviteTpl(lang)
  if (!tpl) return inviteEmailDesign(lang, name, '{{rating}}')
  return mergeName(tpl, name)   // {{rating}} laissé en place → expansé à l'envoi par expandRatingMarker
}
function inviteSmsText (token, lang) { return INVITE[normLang(lang)].sms + `${pub()}/rate/start?t=${encodeURIComponent(token)}` }
function invitePage (token, lang) { const t = INVITE[normLang(lang)]; return shell(t.lead, `<h1>${esc(t.lead)}</h1><p>${esc(t.intro)}</p>${ratingBlock(token)}`) }
// ── Relance « écrire au client » (suite à un avis, surtout mauvais) — brouillon bilingue, PAS de lien d'évaluation ──
const FOLLOWUP = {
  fr: { subject: 'Suite à votre évaluation',
    html: (n) => `<p>Bonjour${n ? ' ' + n : ''},</p><p>Merci d'avoir pris le temps de nous évaluer. On aimerait mieux comprendre votre expérience et voir comment on peut s'améliorer.</p><p>N'hésitez pas à répondre à ce courriel — on est là pour vous aider.</p>`,
    sms: (n) => `Bonjour${n ? ' ' + n : ''}, merci pour votre évaluation. On aimerait mieux comprendre votre expérience — comment peut-on vous aider ?` },
  en: { subject: 'Following up on your review',
    html: (n) => `<p>Hi${n ? ' ' + n : ''},</p><p>Thank you for taking the time to rate us. We'd like to better understand your experience and see how we can improve.</p><p>Feel free to reply to this email — we're here to help.</p>`,
    sms: (n) => `Hi${n ? ' ' + n : ''}, thanks for your review. We'd like to understand your experience better — how can we help?` },
}
function feedbackSubject (lang) { return FOLLOWUP[normLang(lang)].subject }
function feedbackHtml (name, lang) { return FOLLOWUP[normLang(lang)].html(name || '') }
function feedbackSms (name, lang) { return FOLLOWUP[normLang(lang)].sms(name || '') }

async function handle (req, res, method, path, url) {
  try {
    // GET /rate?t=&s= — enregistre la note + redirige (5 → Google, sinon → feedback interne)
    if (path === '/rate' && method === 'GET') {
      const token = url.searchParams.get('t') || ''
      const s = Math.max(0, Math.min(5, parseInt(url.searchParams.get('s'), 10) || 0))
      // Anti-prefetch : les scanners de sécurité / pré-chargeurs de liens (Gmail, Proofpoint, Slackbot…) GETtent les liens
      // et FAUSSERAIENT la note. On n'enregistre PAS sur ces requêtes ; un vrai clic humain n'a pas ces signaux. (On redirige quand même.)
      const ua = String(req.headers['user-agent'] || '')
      const isPrefetch = /prefetch|preview/i.test(String(req.headers.purpose || req.headers['x-purpose'] || req.headers['x-moz'] || '')) ||
        /bot|crawler|spider|scan|preview|proofpoint|mimecast|barracuda|googleimageproxy|google-read-aloud|slackbot|facebookexternalhit|whatsapp|bingbot|linkpreview|curl|wget|python-requests|headless/i.test(ua)
      const m = load(); const rec = m[token]
      if (rec && !isPrefetch) {
        // Filet : si le lien fiche manque (conv non liée à l'envoi), on résout par courriel au moment du clic.
        if (!rec.customer && rec.email) { try { const cr = await lookupCustomersByEmail(rec.email, 1); if (cr && cr[0]) { rec.customer = cr[0].name; if (!rec.name) rec.name = cr[0].customer_name || '' } } catch (e) { /* */ } }
        const changed = rec.stars !== s
        if (changed) { // re-notation permise → historique (évolution) + note courante mise à jour à chaque clic
          rec.history = (rec.history || []).slice(-19)
          rec.history.push({ stars: s, ts: new Date().toISOString() })
          rec.stars = s; rec.stars_ts = new Date().toISOString()
        }
        save(m)
        if (changed) {
          log(`Rating ${s}★ — customer=${rec.customer || '?'} conv=${rec.conv || '?'}`)
          try { sse.broadcast('outbox', 'rating', { token, stars: s, customer: rec.customer, name: rec.name, low: s < 5 }) } catch (e) { /* */ }
        }
      }
      const dest = s >= 5 ? googleUrl() : `${pub()}/rate/feedback?t=${encodeURIComponent(token)}&s=${s}`
      res.writeHead(302, { Location: dest }); return res.end()
    }
    // GET /rate/feedback?t=&s= — page interne (merci + commentaire)
    if (path === '/rate/feedback' && method === 'GET') {
      const token = url.searchParams.get('t') || ''
      const s = Math.max(0, Math.min(5, parseInt(url.searchParams.get('s'), 10) || 0))
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); return res.end(feedbackPage(token, s))
    }
    // GET /rate/start?t= — page d'étoiles AUTONOME (ouverte depuis le lien SMS d'invitation) ; mêmes étoiles cliquables que le courriel.
    if (path === '/rate/start' && method === 'GET') {
      const token = url.searchParams.get('t') || ''
      const rec = load()[token]
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); return res.end(invitePage(token, (rec && rec.lang) || 'fr'))
    }
    // POST /rate/comment {token, comment} — capte le commentaire (avis privé)
    if (path === '/rate/comment' && method === 'POST') {
      const b = await parseBody(req); const token = String(b.token || ''); const comment = String(b.comment || '').trim().slice(0, 2000)
      const m = load(); const rec = m[token]
      if (rec && comment) { // ne JAMAIS écraser un commentaire par du vide (soumission accidentelle / double-envoi)
        rec.comment = comment; rec.comment_ts = new Date().toISOString(); save(m)
        try { sse.broadcast('outbox', 'rating-comment', { token, comment, stars: rec.stars, customer: rec.customer, name: rec.name }) } catch (e) { /* */ }
      }
      return json(res, 200, { ok: true })
    }
    return json(res, 404, { error: 'not found' })
  } catch (e) { log('rating handle: ' + e.message); return json(res, 500, { error: e.message }) }
}

// Liste des évaluations (récentes d'abord) — pour la vue Ops « Évaluations » (basses prioritaires).
function listRatings ({ limit = 150, customer = '' } = {}) {
  const m = load()
  let out = Object.entries(m)
    .map(([token, r]) => ({ token, ...r }))
    .filter(r => r.stars != null || r.comment) // seulement celles où le client a agi
  if (customer) out = out.filter(r => r.customer === customer) // évaluations D'UN client précis (pour sa fiche)
  out.sort((a, b) => String(b.stars_ts || b.comment_ts || b.created || '').localeCompare(String(a.stars_ts || a.comment_ts || a.created || '')))
  const low = out.filter(r => r.stars != null && r.stars < 5).length
  return { ratings: out.slice(0, limit), total: out.length, low }
}

// Supprimer une évaluation (ex. avis de test → pollution visuelle dans /Évaluations).
function deleteRating (token) { const m = load(); if (m[token]) { delete m[token]; save(m); return true } return false }
module.exports = { handle, ratingBlock, newRatingToken, expandRatingMarker, listRatings, deleteRating, inviteSubject, inviteEmailHtml, inviteEmailMarkerHtml, inviteSmsText, normLang, feedbackSubject, feedbackHtml, feedbackSms }
