'use strict'
const cfg = require('./config')
const { log, erpFetch } = require('./helpers')
const ALL_TOOLS = require('./agent-tools.json')

// agent-tools.json est le registre UNIQUE partagé (parité UI↔NL). Chaque outil porte des
// métadonnées HORS du sous-objet `function` : audience ('customer'|'staff'), mode ('read'|'write'),
// permission, title. On les RETIRE avant l'appel modèle (l'endpoint OpenAI-compat de Gemini ne
// reçoit que {type, function}) et on FILTRE par audience :
//   - le répondeur SMS/voix client ne voit QUE les outils 'customer' (sécurité : un client ne doit
//     jamais pouvoir déclencher un outil d'écriture STAFF comme create_recurring_shift) ;
//   - le copilote STAFF (lib/staff-agent.js) charge les outils 'staff'.
const toolsForModel = (list) => list.map(t => ({ type: t.type, function: t.function }))
const toolsByAudience = (audience) => ALL_TOOLS.filter(t => (t.audience || 'customer') === audience)
const TOOLS = toolsForModel(toolsByAudience('customer'))

let _convContext = {}

const erpList = async (doctype, fields, filters, { limit = 20, orderBy, wrapKey } = {}) => {
  let url = `/api/resource/${doctype}?filters=${encodeURIComponent(JSON.stringify(filters))}&fields=${encodeURIComponent(JSON.stringify(fields))}&limit_page_length=${limit}`
  if (orderBy) url += `&order_by=${orderBy}`
  const r = await erpFetch(url)
  if (r.status !== 200) return { error: `Failed to fetch ${doctype.toLowerCase()}` }
  const data = r.data.data || []
  return wrapKey ? { [wrapKey]: data } : data
}

const getCustomerInfo = async (id) => {
  const r = await erpFetch(`/api/resource/Customer/${encodeURIComponent(id)}`)
  if (r.status !== 200) return { error: 'Customer not found' }
  const c = r.data.data
  const info = {
    name: c.name, customer_name: c.customer_name, customer_type: c.customer_type,
    territory: c.territory, language: c.language,
    cell_phone: c.cell_phone, tel_home: c.tel_home, tel_office: c.tel_office,
    email_billing: c.email_billing, contact_name: c.contact_name_legacy,
    is_commercial: c.is_commercial, is_bad_payer: c.is_bad_payer,
    disabled: c.disabled, creation: c.creation,
  }
  // Include churn/retention context for AI decision-making
  if (c.churn_status) info.churn_status = c.churn_status
  if (c.cancel_reason) info.cancel_reason = c.cancel_reason
  if (c.cancel_competitor) info.cancel_competitor = c.cancel_competitor
  if (c.cancel_date) info.cancel_date = c.cancel_date
  if (c.cancel_notes) info.cancel_notes = c.cancel_notes
  if (c.churn_risk_score) info.churn_risk_score = c.churn_risk_score
  return info
}

const getSubscriptions = (cid) => erpList('Subscription',
  ['name', 'item_code', 'custom_description', 'actual_price', 'billing_frequency', 'status', 'service_location', 'start_date'],
  { party_type: 'Customer', party: cid },
  { orderBy: 'status+asc', wrapKey: 'subscriptions' })

const getInvoices = (cid, limit = 5) => erpList('Sales Invoice',
  ['name', 'posting_date', 'grand_total', 'outstanding_amount', 'status', 'due_date'],
  { customer: cid, docstatus: 1 },
  { limit, orderBy: 'posting_date+desc', wrapKey: 'invoices' })

const getOutstandingBalance = async (cid) => {
  const data = await erpList('Sales Invoice', ['outstanding_amount'],
    { customer: cid, docstatus: 1, outstanding_amount: ['>', 0] }, { limit: 0 })
  if (data.error) return data
  const total = data.reduce((s, i) => s + (parseFloat(i.outstanding_amount) || 0), 0)
  return { outstanding_balance: Math.round(total * 100) / 100, unpaid_invoices: data.length }
}

const getServiceLocations = (cid) => erpList('Service Location',
  ['name', 'address_line', 'city', 'postal_code', 'location_name', 'contact_name', 'contact_phone', 'connection_type', 'olt_port', 'network_id', 'status'],
  { customer: cid }, { wrapKey: 'locations' })

const getEquipment = (cid) => erpList('Service Equipment',
  ['name', 'serial_number', 'mac_address', 'ip_address', 'brand', 'model', 'status', 'service_location', 'firmware_version'],
  { customer: cid }, { wrapKey: 'equipment' })

const getOpenTickets = (cid) => erpList('Issue',
  ['name', 'subject', 'status', 'priority', 'opening_date', 'issue_type'],
  { customer: cid, status: ['in', ['Open', 'Replied']] },
  { limit: 10, orderBy: 'opening_date+desc', wrapKey: 'tickets' })

const checkDeviceStatus = async (serial) => {
  const { getCached, fetchDeviceDetails } = require('./devices')
  const cached = getCached(serial)
  let summary
  if (cached && cached.summary.firmware && (Date.now() - new Date(cached.updatedAt).getTime()) < 120000) {
    summary = cached.summary
    log(`Device ${serial}: using cached data (${Math.floor((Date.now() - new Date(cached.updatedAt).getTime()) / 1000)}s old)`)
  } else {
    summary = await fetchDeviceDetails(serial)
    if (!summary || !summary.serial) return { error: 'Device not found in ACS', serial }
  }

  const lastInform = summary.lastInform ? new Date(summary.lastInform) : null
  const minutesAgo = lastInform ? Math.floor((Date.now() - lastInform.getTime()) / 60000) : null
  // Use 20 min threshold — TR-069 inform intervals vary (5m, 10m, 15m+)
  // 10 min was too aggressive and flagged online devices as offline
  let online = minutesAgo !== null && minutesAgo < 20

  // Cross-check with SNMP OLT data — SNMP is real-time and more reliable than TR-069 lastInform
  let snmpStatus = null
  try {
    const oltSnmp = require('./olt-snmp')
    const onu = oltSnmp.getOnuBySerial(serial)
    if (onu) {
      snmpStatus = onu.status
      if (onu.status === 'online' && !online) {
        // SNMP says online but TR-069 says offline → trust SNMP
        log(`Device ${serial}: TR-069 says offline (${minutesAgo}m) but SNMP says online — trusting SNMP`)
        online = true
      }
    }
  } catch (_) { /* SNMP not available, rely on TR-069 only */ }

  let uptimeStr = null
  if (summary.uptime) {
    const h = Math.floor(summary.uptime / 3600)
    const d = Math.floor(h / 24)
    uptimeStr = d > 0 ? `${d} jours ${h % 24}h` : `${h}h${Math.floor((summary.uptime % 3600) / 60)}m`
  }

  const prev = getCached(serial)?.previous
  let stateChange = null
  if (prev) {
    const prevInform = prev.lastInform ? new Date(prev.lastInform) : null
    const prevOnline = prevInform ? (Date.now() - prevInform.getTime()) < 600000 : false
    if (prevOnline && !online) stateChange = 'went_offline'
    else if (!prevOnline && online) stateChange = 'came_online'
  }

  return {
    serial: summary.serial,
    model: `${summary.manufacturer} ${summary.model}`.trim(),
    firmware: summary.firmware, online, state_change: stateChange,
    snmp_status: snmpStatus,
    last_seen: lastInform ? lastInform.toISOString() : null,
    last_seen_minutes_ago: minutesAgo, uptime: uptimeStr,
    wan_ip: summary.ip,
    rx_power_dbm: summary.rxPower != null ? (summary.rxPower / 1000).toFixed(1) + ' dBm' : null,
    tx_power_dbm: summary.txPower != null ? (summary.txPower / 1000).toFixed(1) + ' dBm' : null,
    wifi_ssid: summary.ssid, wifi_clients: summary.wifi?.totalClients || 0,
    connected_devices: summary.hostsCount, optical_status: summary.opticalStatus,
  }
}

// ── Background connection verification ─────────────────────────────────────
// Polls device status 3 times over ~90s, then pushes a diagnostic message
// into the conversation. The agent tool returns immediately so the AI can
// tell the customer "we're checking".
const activeChecks = new Map()

const startConnectionCheck = async ({ serial_number, customer_id }) => {
  const token = _convContext.token
  if (!token) return { error: 'No conversation context' }
  if (activeChecks.has(token + ':' + serial_number)) {
    return { status: 'already_checking', message: 'Une vérification est déjà en cours pour cet appareil.' }
  }

  activeChecks.set(token + ':' + serial_number, true)

  // Run background polling (don't await — returns immediately)
  runBackgroundCheck(token, serial_number, customer_id).catch(e => {
    log(`Background check error for ${serial_number}:`, e.message)
    activeChecks.delete(token + ':' + serial_number)
  })

  return {
    status: 'checking',
    message: 'Vérification de la connexion lancée. Le diagnostic sera envoyé dans quelques instants.'
  }
}

async function runBackgroundCheck (convToken, serial, customerId) {
  const { getCached, fetchDeviceDetails } = require('./devices')
  const oltSnmp = require('./olt-snmp')
  const { getConversation, addMessage } = require('./conversation')
  const { sendSmsInternal } = require('./twilio')
  const results = []

  for (let attempt = 0; attempt < 3; attempt++) {
    if (attempt > 0) await new Promise(r => setTimeout(r, 30000)) // 30s between checks

    let tr069Online = false, snmpOnline = false, snmpData = null, tr069Data = null
    try {
      const summary = await fetchDeviceDetails(serial)
      if (summary?.lastInform) {
        const age = (Date.now() - new Date(summary.lastInform).getTime()) / 60000
        tr069Online = age < 20
        tr069Data = { age: Math.round(age), uptime: summary.uptime, ip: summary.ip, rxPower: summary.rxPower }
      }
    } catch (_) {}

    try {
      const onu = oltSnmp.getOnuBySerial(serial)
      if (onu) {
        snmpOnline = onu.status === 'online'
        snmpData = {
          status: onu.status, lastOfflineCause: onu.lastOfflineCause,
          rxPowerOlt: onu.rxPowerOlt, rxPowerOnu: onu.rxPowerOnu,
          port: onu.port, oltName: onu.oltName
        }
      }
    } catch (_) {}

    const online = tr069Online || snmpOnline
    results.push({ attempt: attempt + 1, online, tr069Online, snmpOnline, tr069Data, snmpData, ts: new Date().toISOString() })
    log(`BG check ${serial} #${attempt + 1}: online=${online} (TR069=${tr069Online}, SNMP=${snmpOnline})`)

    // If confirmed online on first check, no need to keep polling
    if (online && attempt === 0) break
  }

  activeChecks.delete(convToken + ':' + serial)

  // Analyze results
  const onlineCount = results.filter(r => r.online).length
  const lastResult = results[results.length - 1]
  const conv = getConversation(convToken)

  let diagText
  if (onlineCount === results.length) {
    // All checks say online
    diagText = 'Bonne nouvelle! Après vérification, votre modem est bien en ligne et fonctionnel. Si vous éprouvez des lenteurs, n\'hésitez pas à nous écrire.'
  } else if (onlineCount > 0) {
    // Intermittent
    diagText = 'Votre connexion semble intermittente — le modem alterne entre en ligne et hors ligne. Je vous suggère de débrancher le modem de la prise électrique pendant 30 secondes, puis de le rebrancher. Attendez environ 3 minutes pour qu\'il redémarre complètement.'
  } else {
    // Confirmed offline after all checks
    const cause = lastResult.snmpData?.lastOfflineCause || ''
    const lc = cause.toLowerCase()
    if (lc.includes('dying') || lc.includes('gasp')) {
      diagText = 'Votre modem semble hors ligne depuis un certain temps. La cause détectée est une perte d\'alimentation (Dying Gasp). Veuillez vérifier que le modem est bien branché à une prise fonctionnelle et que le bouton on/off est en position ON.'
    } else if (lc.includes('branch') || lc.includes('fiber cut') || lc.includes('fibre')) {
      diagText = 'Après vérification, une coupure de fibre a été détectée. Un technicien sera assigné pour intervenir. Nous vous tiendrons informé.'
    } else {
      diagText = 'Votre modem semble hors ligne depuis un certain temps. Je vous suggère de le débrancher de la prise électrique pendant 30 secondes, puis de le rebrancher. Attendez environ 3 minutes pour qu\'il redémarre complètement. Si le problème persiste, un agent prendra en charge votre dossier.'
    }
  }

  if (conv) {
    addMessage(conv, { from: 'agent', text: diagText, via: 'ai' })
    // If customer came via SMS, also send SMS
    const lastCustomerMsg = [...conv.messages].reverse().find(m => m.from === 'customer')
    if (lastCustomerMsg?.via === 'sms' && conv.phone) {
      const chatLink = `${cfg.CLIENT_PUBLIC_URL}/c/${conv.token}`
      sendSmsInternal(conv.phone, `${diagText}\n\nContinuer en ligne: ${chatLink}`, conv.customer)
        .then(sid => { if (sid) conv.smsCount = (conv.smsCount || 0) + 1 })
        .catch(e => log('BG check SMS error:', e.message))
    }
    log(`BG check complete for ${serial}: ${onlineCount}/${results.length} online → sent diagnostic`)
  }
}

const createTicket = async ({ customer_id, subject, description, priority }) => {
  const r = await erpFetch('/api/resource/Issue', {
    method: 'POST',
    body: JSON.stringify({
      customer: customer_id, subject,
      description: description || subject,
      priority: priority || 'Medium', status: 'Open',
    }),
  })
  if (r.status < 200 || r.status >= 300) return { error: 'Failed to create ticket' }
  return { success: true, ticket_id: r.data?.data?.name, message: 'Ticket created successfully' }
}

const getChatLink = () => {
  if (_convContext.token) {
    return { link: `${cfg.CLIENT_PUBLIC_URL}/c/${_convContext.token}`, message: 'Voici le lien pour continuer la conversation en ligne' }
  }
  return { error: 'No active conversation token available' }
}

const checkOnuStatus = async (serial) => {
  const oltSnmp = require('./olt-snmp')
  const { getDeviceEvents } = require('./devices')

  // Get live ONU data from OLT SNMP cache
  const onu = oltSnmp.getOnuBySerial(serial)
  const events = await getDeviceEvents(serial, 10)

  if (!onu) {
    // No ONU found in OLT, check events only
    const recentEvents = events.map(e => ({
      event: e.event, reason: e.reason,
      time: e.created_at, details: e.details,
    }))
    return { error: 'ONU not found in OLT SNMP cache', serial, recent_events: recentEvents }
  }

  const result = {
    serial: onu.serial, status: onu.status,
    olt: onu.oltName || null, port: onu.port,
    rx_power_olt: onu.rxPowerOlt != null ? (onu.rxPowerOlt / 100).toFixed(2) + ' dBm' : null,
    tx_power_olt: onu.txPowerOlt != null ? (onu.txPowerOlt / 100).toFixed(2) + ' dBm' : null,
    rx_power_onu: onu.rxPowerOnu != null ? (onu.rxPowerOnu / 100).toFixed(2) + ' dBm' : null,
    distance_m: onu.distance || null,
    temperature: onu.temperature || null,
    last_offline_cause: onu.lastOfflineCause || null,
    uptime: onu.uptime || null,
    recent_events: events.slice(0, 5).map(e => ({
      event: e.event, reason: e.reason, time: e.created_at,
    })),
  }

  // Classify the alarm
  const cause = (onu.lastOfflineCause || '').toLowerCase()
  if (cause.includes('dying') || cause.includes('gasp')) {
    result.alarm_type = 'dying_gasp'
    result.alarm_description = 'Perte d\'alimentation électrique (Dying Gasp) — le modem a perdu le courant'
  } else if (cause.includes('branch') || cause.includes('fiber cut') || cause.includes('fibre')) {
    result.alarm_type = 'branch_fiber_cut'
    result.alarm_description = 'Coupure de fibre détectée (Branch Fiber Cut) — intervention terrain requise'
  } else if (cause.includes('losi') || cause.includes('los')) {
    result.alarm_type = 'loss_of_signal'
    result.alarm_description = 'Perte de signal optique (LOSi) — possible coupure ou dégradation fibre'
  } else if (cause.includes('lobi') || cause.includes('lob')) {
    result.alarm_type = 'loss_of_burst'
    result.alarm_description = 'Perte de burst (LOBi) — problème émetteur ONU'
  } else if (onu.status === 'offline' && cause) {
    result.alarm_type = 'other_offline'
    result.alarm_description = `Hors ligne: ${onu.lastOfflineCause}`
  }

  // ── Port neighbor correlation (automatic when offline) ──
  if (onu.status === 'offline' && onu.port) {
    const portData = oltSnmp.getPortNeighbors(serial)
    if (portData) {
      const neighbors = portData.neighbors || []
      const offlineNeighbors = neighbors.filter(n => n.status === 'offline')
      const onlineNeighbors = neighbors.filter(n => n.status === 'online')
      result.port_context = {
        total_on_port: neighbors.length + 1,
        online_on_port: onlineNeighbors.length,
        offline_on_port: offlineNeighbors.length + 1, // +1 for target
        other_offline_causes: offlineNeighbors.map(n => oltSnmp.classifyOfflineCause(n.lastOfflineCause)),
        is_isolated: offlineNeighbors.length === 0,
        is_mass_outage: offlineNeighbors.length >= 3,
      }
      if (result.port_context.is_mass_outage) {
        result.port_context.mass_outage_warning = `ATTENTION: ${offlineNeighbors.length + 1} clients hors ligne sur le même port ${onu.port}. Probable panne en amont.`
      }
    }
  }

  return result
}

const execTool = async (name, input) => {
  try {
    const handlers = {
      get_customer_info: () => getCustomerInfo(input.customer_id),
      get_subscriptions: () => getSubscriptions(input.customer_id),
      get_invoices: () => getInvoices(input.customer_id, input.limit || 5),
      get_outstanding_balance: () => getOutstandingBalance(input.customer_id),
      get_service_locations: () => getServiceLocations(input.customer_id),
      get_equipment: () => getEquipment(input.customer_id),
      check_device_status: () => checkDeviceStatus(input.serial_number),
      check_onu_status: () => checkOnuStatus(input.serial_number),
      analyze_outage: () => require('./ai').analyzeOutage(input.serial_number),
      start_connection_check: () => startConnectionCheck({ serial_number: input.serial_number, customer_id: input.customer_id }),
      get_open_tickets: () => getOpenTickets(input.customer_id),
      create_ticket: () => createTicket(input),
      get_chat_link: () => getChatLink(),
      create_dispatch_job: () => require('./dispatch').agentCreateDispatchJob(input),
    }
    return handlers[name] ? await handlers[name]() : { error: 'Unknown tool: ' + name }
  } catch (e) {
    log(`Tool ${name} error:`, e.message)
    return { error: 'Tool execution failed: ' + e.message }
  }
}

// ── Build system prompt dynamically from agent-flows.json ────────────────────
const fs = require('fs')
const path = require('path')
const FLOWS_PATH = path.join(__dirname, '..', 'data', 'agent-flows.json')
const FLOWS_DEFAULT = path.join(__dirname, 'agent-flows.json')

let _flows = null
function loadFlows () {
  try {
    // Try data/ (writable) first, fall back to lib/ (read-only default)
    const fp = fs.existsSync(FLOWS_PATH) ? FLOWS_PATH : FLOWS_DEFAULT
    _flows = JSON.parse(fs.readFileSync(fp, 'utf8'))
    log(`Agent flows loaded from ${fp} (v${_flows.version || 1}, ${_flows.intents?.length || 0} intents)`)
  } catch (e) { log('Failed to load agent-flows.json:', e.message) }
  return _flows
}
function getFlows () { return _flows || loadFlows() }

function buildSystemPrompt () {
  const f = getFlows()
  if (!f) return 'Tu es un assistant de support client. Réponds en français.'

  const lines = [`Tu es l'assistant virtuel de ${f.persona.name}, ${f.persona.role}.`, '', 'Ton rôle:', '- Répondre aux questions des clients', '- Diagnostiquer les problèmes techniques', '- Créer des tickets ou dispatcher des techniciens quand nécessaire', `- Ton ton: ${f.persona.tone}`, '']
  lines.push('Règles:')
  f.persona.rules.forEach(r => lines.push(`- ${r}`))
  lines.push('- IMPORTANT: Ne JAMAIS dire au client que sa connexion est hors ligne sur la base d\'un seul check. Si check_device_status retourne online=false, appelle start_connection_check pour lancer une vérification en arrière-plan et dis au client "Nous vérifions votre connexion, un instant...". Le résultat sera envoyé automatiquement.')
  lines.push('- Si check_device_status retourne online=true, tu peux confirmer immédiatement que la connexion est fonctionnelle.')
  lines.push('- Si check_onu_status retourne port_context.is_mass_outage=true, informe le client qu\'une panne affecte son secteur et que nos équipes sont mobilisées. Utilise analyze_outage pour obtenir plus de détails.')
  lines.push('')
  lines.push('RÉTENTION CLIENT:')
  lines.push('- Si get_customer_info retourne un churn_status ou cancel_reason, le client est un ancien abonné ou à risque.')
  lines.push('- Si cancel_reason = "Compétiteur - Promotion" et cancel_competitor est renseigné, propose une comparaison de prix à long terme (les promos finissent, notre prix reste stable).')
  lines.push('- Si cancel_reason = "Qualité WiFi", propose un diagnostic WiFi et mentionne nos solutions mesh/répéteur/câblage.')
  lines.push('- Si cancel_reason = "Prix trop élevé", oriente vers les forfaits disponibles et les rabais de fidélité possibles.')
  lines.push('- Ne JAMAIS mentionner directement que tu vois les données de rétention. Sois naturel et bienveillant.')

  for (const intent of f.intents) {
    lines.push('', `FLOW: ${intent.label.toUpperCase()}`)
    lines.push(`Déclencheur: ${intent.trigger}`)
    lines.push('Étapes:')
    for (const step of intent.steps) {
      const prefix = step.parent ? `  Si ${step.branch}: ` : '- '
      if (step.type === 'tool') {
        lines.push(`${prefix}Utilise ${step.tool}${step.note ? ` (${step.note})` : ''}`)
      } else if (step.type === 'condition') {
        lines.push(`${prefix}Vérifier: ${step.field} ${step.op} ${step.value}? [${step.label}]`)
      } else if (step.type === 'switch') {
        lines.push(`${prefix}Selon ${step.field}:`)
      } else if (step.type === 'respond') {
        lines.push(`${prefix}${step.label}: ${step.message}`)
        if (step.note) lines.push(`    NOTE: ${step.note}`)
      } else if (step.type === 'action') {
        lines.push(`${prefix}${step.label}: appeler ${step.action}${step.params ? ' avec ' + JSON.stringify(step.params) : ''}`)
        if (step.message) lines.push(`    → Dire au client: "${step.message}"`)
      } else if (step.type === 'goto') {
        lines.push(`${prefix}${step.label}`)
      }
    }
  }

  return lines.join('\n')
}

// Initialize on load
loadFlows()

// Répondeur SMS → routé par tâche 'sms' (PII + volume) via lib/ai.js (repli Gemini auto). Boucle tool-calls inchangée : on ré-emballe en {choices:[{message}]}.
const geminiChat = async (messages, tools) => {
  const msg = await require('./ai').chat({ task: 'sms', messages, tools, maxTokens: 500, wantMessage: true })
  return { choices: [{ message: msg }] }
}

const runAgent = async (customerId, customerName, messageHistory, { token } = {}) => {
  _convContext = { token: token || null }
  if (!cfg.AI_API_KEY) return 'Désolé, le service d\'assistance automatique n\'est pas disponible pour le moment. Un agent vous répondra sous peu.'

  const messages = [{ role: 'system', content: buildSystemPrompt() }]
  for (const m of messageHistory) {
    if (m.from === 'customer') messages.push({ role: 'user', content: m.text })
    else if (m.from === 'agent') messages.push({ role: 'assistant', content: m.text })
  }

  const firstUser = messages.find(m => m.role === 'user')
  if (firstUser) firstUser.content = `[Contexte: Client ${customerName} (${customerId})]\n\n${firstUser.content}`

  let response
  try { response = await geminiChat(messages, TOOLS) }
  catch (e) { log('Gemini API error:', e.message); return 'Désolé, une erreur est survenue. Un agent vous répondra sous peu.' }

  const currentMessages = [...messages]
  for (let i = 0; i < 5; i++) {
    const choice = response.choices?.[0]
    if (!choice || choice.finish_reason !== 'tool_calls') break
    const toolCalls = choice.message?.tool_calls
    if (!toolCalls?.length) break

    currentMessages.push(choice.message)
    for (const tc of toolCalls) {
      const args = JSON.parse(tc.function.arguments || '{}')
      log(`Agent tool call: ${tc.function.name}(${JSON.stringify(args)})`)
      const result = await execTool(tc.function.name, args)
      log(`Agent tool result: ${tc.function.name} →`, JSON.stringify(result).substring(0, 200))
      currentMessages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result) })
    }

    try { response = await geminiChat(currentMessages, TOOLS) }
    catch (e) { log('Gemini API error (tool loop):', e.message); return 'Désolé, une erreur est survenue pendant le diagnostic. Un agent vous répondra sous peu.' }
  }

  return response.choices?.[0]?.message?.content || 'Un agent vous répondra sous peu.'
}

// ── HTTP handler for /agent/* endpoints ──────────────────────────────────────
const handleAgentApi = async (req, res, method, urlPath) => {
  const { json: jsonRes, parseBody: parse } = require('./helpers')

  if (urlPath === '/agent/flows' && method === 'GET') {
    const flows = getFlows()
    return jsonRes(res, 200, flows || { error: 'Flows not loaded' })
  }

  if (urlPath === '/agent/flows' && method === 'PUT') {
    const body = await parse(req)
    try {
      // Validate structure
      if (!body.persona || !body.intents || !Array.isArray(body.intents)) {
        return jsonRes(res, 400, { error: 'Invalid flow: needs persona + intents[]' })
      }
      body.version = (getFlows()?.version || 0) + 1
      body.updated_at = new Date().toISOString()
      // Write to data/ (writable volume)
      const dir = path.dirname(FLOWS_PATH)
      if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
      fs.writeFileSync(FLOWS_PATH, JSON.stringify(body, null, 2))
      _flows = body
      log(`Agent flows updated (v${body.version}, ${body.intents.length} intents)`)
      return jsonRes(res, 200, { ok: true, version: body.version })
    } catch (e) {
      return jsonRes(res, 500, { error: e.message })
    }
  }

  if (urlPath === '/agent/prompt' && method === 'GET') {
    return jsonRes(res, 200, { prompt: buildSystemPrompt() })
  }

  const { json: j } = require('./helpers')
  j(res, 404, { error: 'Agent endpoint not found' })
}

module.exports = { runAgent, TOOLS, execTool, handleAgentApi, getFlows, toolsForModel, toolsByAudience }
