const express = require('express');
const fetch = require('node-fetch');
const fs = require('fs');
const path = require('path');

const app = express();
app.use(express.json());

// =============================================================================
// Configuración
// =============================================================================
const NAN_API_KEY = process.env.NAN_API_KEY || 'sk-gJ8OWbZVGeWy9ns_H00cxA';
const NAN_BASE_URL = process.env.NAN_BASE_URL || 'https://api.nan.builders/v1';
const HERMES_API_URL = process.env.HERMES_API_URL || 'http://localhost:8080/v1/chat/completions';
const HERMES_API_KEY = process.env.HERMES_API_KEY || 'sk-skeen-hermes-2026';
const ODOO_URL = process.env.ODOO_URL || 'http://localhost:8069';
const WACRM_URL = process.env.WACRM_URL || 'http://localhost:3000';
const WACRM_API_KEY = process.env.WACRM_API_KEY || 'wacrm_live_ffac5531d76645acb239dba71ace6d1c';

const SKEEN_SERVICES_JSON = process.env.SKEEN_SERVICES_JSON || '/root/skeen-roadmap-hermes/hermes/skills/skeen-services.json';
const MEMORY_DIR = process.env.MEMORY_DIR || '/root/skeen-roadmap-hermes/hermes/memory';

// Asegurar directorio de memoria
if (!fs.existsSync(MEMORY_DIR)) {
  fs.mkdirSync(MEMORY_DIR, { recursive: true });
}

// =============================================================================
// Datos de la clínica
// =============================================================================
const CLINIC_NAME = 'SKEEN Derma Experts';
const CLINIC_ADDRESS = 'Blvd. Popotla 123, Playas de Rosarito, BC, México';
const CLINIC_HOURS = 'Lun-Vie 9:00-18:00, Sáb 9:00-14:00';
const CLINIC_PHONE = '+52 (664) 123-4567';

// =============================================================================
// Helpers
// =============================================================================
function memoryPath(phone) {
  const safe = (phone || 'unknown').replace(/[^0-9]/g, '_');
  return path.join(MEMORY_DIR, `${safe}.json`);
}

function loadMemory(phone) {
  try {
    const p = memoryPath(phone);
    if (fs.existsSync(p)) {
      return JSON.parse(fs.readFileSync(p, 'utf8'));
    }
  } catch (e) {
    console.error('[Memory] Error cargando memoria:', e.message);
  }
  return { phone, context: {}, history: [], created_at: new Date().toISOString() };
}

function saveMemory(phone, data) {
  try {
    fs.writeFileSync(memoryPath(phone), JSON.stringify(data, null, 2));
  } catch (e) {
    console.error('[Memory] Error guardando memoria:', e.message);
  }
}

function loadServicesCatalog() {
  try {
    const raw = JSON.parse(fs.readFileSync(SKEEN_SERVICES_JSON, 'utf8'));
    if (raw && Array.isArray(raw.servicios)) {
      return raw.servicios.map(s => ({
        id: s.id,
        name: s.nombre,
        category: s.categoria,
        price: s.precio,
        duration_min: s.duracion_min,
        description: s.descripcion,
        contraindications: s.contraindicaciones,
      }));
    }
    return raw || [];
  } catch (e) {
    console.error('[Catalog] Error cargando servicios:', e.message);
    return [];
  }
}

function normalizePhone(phone) {
  if (!phone) return '';
  let p = phone.replace(/[^0-9+]/g, '');
  if (p.startsWith('52') && p.length > 10) return '+' + p;
  if (p.startsWith('+52')) return p;
  if (p.length === 10) return '+52' + p;
  return phone;
}

function formatDateEs(dateStr) {
  // Interpretar dateStr (YYYY-MM-DD) como fecha en America/Tijuana, no UTC
  const [y, m, d] = dateStr.split('-').map(Number);
  // Usar 07:00 UTC como aproximación segura a medianoche en Tijuana (UTC-7/UTC-8)
  const dt = new Date(Date.UTC(y, m - 1, d, 7, 0, 0));
  return dt.toLocaleDateString('es-MX', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'America/Tijuana' });
}

function formatTimeAmPm(timeStr) {
  const [h, m] = timeStr.split(':').map(Number);
  const ampm = h >= 12 ? 'PM' : 'AM';
  const h12 = h % 12 || 12;
  return `${h12}:${String(m).padStart(2, '0')} ${ampm}`;
}

function getTodayTz() {
  return new Date().toLocaleDateString('en-CA', { timeZone: 'America/Tijuana' });
}

function addDaysToIso(iso, days) {
  const [y, m, d] = iso.split('-').map(Number);
  const dt = new Date(Date.UTC(y, m - 1, d, 7, 0, 0));
  dt.setUTCDate(dt.getUTCDate() + days);
  return formatDateIsoTz(dt);
}

function getNextDayOfWeekIso(fromIso, dayIndex) {
  const [y, m, d] = fromIso.split('-').map(Number);
  const dt = new Date(Date.UTC(y, m - 1, d, 7, 0, 0));
  const current = dt.getUTCDay();
  let diff = (dayIndex - current + 7) % 7;
  if (diff === 0) diff = 7;
  dt.setUTCDate(dt.getUTCDate() + diff);
  return formatDateIsoTz(dt);
}

function formatDateIsoTz(d) {
  return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`;
}

// =============================================================================
// LLM / Hermes
// =============================================================================
async function callHermes(messages, sessionId = null) {
  const body = {
    model: 'qwen3.6',
    messages,
    temperature: 0.6,
    max_tokens: 600,
  };
  if (sessionId) {
    body.session_id = sessionId;
  }

  const response = await fetch(HERMES_API_URL, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${HERMES_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Hermes API error: ${response.status} ${error}`);
  }

  const data = await response.json();
  return data.choices?.[0]?.message?.content || data.choices?.[0]?.text || data.response || 'Lo siento, no pude procesar tu mensaje.';
}

async function callNanBuilders(messages) {
  const response = await fetch(`${NAN_BASE_URL}/chat/completions`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${NAN_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'qwen3.6',
      messages,
      temperature: 0.6,
      max_tokens: 500,
    }),
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Nan API error: ${response.status} ${error}`);
  }

  const data = await response.json();
  return data.choices[0]?.message?.content || 'Lo siento, no pude procesar tu mensaje.';
}

// =============================================================================
// Odoo Frontend API
// =============================================================================
async function odooGet(endpoint, params = {}) {
  const url = new URL(`${ODOO_URL}${endpoint}`);
  Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null) url.searchParams.set(k, v); });
  const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'application/json' } });
  if (!response.ok) throw new Error(`Odoo GET ${endpoint} error: ${response.status}`);
  return await response.json();
}

async function odooPost(endpoint, body = {}) {
  const response = await fetch(`${ODOO_URL}${endpoint}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
    body: JSON.stringify(body),
  });
  const text = await response.text();
  if (!response.ok) {
    console.error(`[Odoo] POST ${endpoint} ${response.status}: ${text.substring(0, 300)}`);
    throw new Error(`Odoo POST ${endpoint} error: ${response.status} ${text}`);
  }
  return JSON.parse(text);
}

async function odooPut(endpoint, body = {}) {
  const response = await fetch(`${ODOO_URL}${endpoint}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!response.ok) throw new Error(`Odoo PUT ${endpoint} error: ${response.status}`);
  return await response.json();
}

// =============================================================================
// WACRM
// =============================================================================
async function sendWacrmMessage(to, text, simulate = false) {
  const endpoint = simulate ? `${WACRM_URL}/api/v1/messages/simulate` : `${WACRM_URL}/api/v1/messages`;
  const response = await fetch(endpoint, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${WACRM_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ to, type: 'text', text }),
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`WACRM send error: ${response.status} ${error}`);
  }
  return await response.json();
}

async function sendWacrmMessageWithFallback(to, text) {
  try {
    const result = await sendWacrmMessage(to, text, false);
    console.log(`[WACRM] sent message to ${to}:`, JSON.stringify(result).substring(0, 200));
    return result;
  } catch (error) {
    console.error(`[WACRM] send failed to ${to}:`, error.message);
    const msg = error.message || '';
    if (msg.includes('Meta API error') || msg.includes('Invalid OAuth') || msg.includes('not registered') || msg.includes('not connected')) {
      console.warn('[Bridge] Meta send failed, falling back to simulate endpoint');
      return await sendWacrmMessage(to, text, true);
    }
    throw error;
  }
}

// =============================================================================
// Pacientes
// =============================================================================
async function findPatientByPhone(phone) {
  const data = await odooGet('/skeen/frontend/v1/patients', { search: phone });
  return data.patients?.find(p => p.phone === phone || normalizePhone(p.phone) === normalizePhone(phone));
}

async function createPatient(phone, name) {
  return await odooPost('/skeen/frontend/v1/patients', {
    name: name || 'Paciente WhatsApp',
    phone,
    source: 'whatsapp',
  });
}

async function findOrCreatePatient(phone, name = null) {
  const existing = await findPatientByPhone(phone);
  if (existing) return existing;
  const created = await createPatient(phone, name);
  return created.patient;
}

// =============================================================================
// Servicios
// =============================================================================
async function getServices() {
  const data = await odooGet('/skeen/frontend/v1/services');
  return data.services || [];
}

function getCleanServices() {
  const catalog = loadServicesCatalog();
  if (catalog && catalog.length > 0) return catalog;
  return [];
}

function norm(s) {
  return (s || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').trim();
}

// Resuelve un servicio SIEMPRE con el id vigente de Odoo. El catálogo curado
// (skeen-services.json) solo enriquece (keywords/descripcion); su id NUNCA se usa
// directamente porque queda obsoleto tras reimportar. Así el bridge es robusto
// ante resets/reimportaciones de skeen_servicio.
function matchServiceByText(text, services) {
  const t = norm(text);
  const live = services || [];
  // 1) Servicios vivos de Odoo = fuente de verdad de ids
  for (const s of live) {
    const n = norm(s.name);
    if (n && (n.includes(t) || t.includes(n))) return s;
  }
  // 2) Catálogo curado: matchea por nombre/keywords y resuelve el id LIVE por nombre
  for (const c of getCleanServices()) {
    const cn = norm(c.name);
    const kwHit = c.keywords && c.keywords.some(k => {
      const kl = norm(k);
      return kl && (kl.includes(t) || t.includes(kl));
    });
    if ((cn && (cn.includes(t) || t.includes(cn))) || kwHit) {
      const hit = live.find(s => norm(s.name) === cn)
        || live.find(s => { const sn = norm(s.name); return sn && (sn.includes(cn) || cn.includes(sn)); });
      if (hit) return { ...c, id: hit.id, price: hit.price, duration_min: hit.duration_min, name: hit.name };
      // sin equivalente vivo: no devolver id viejo
    }
  }
  return null;
}

// =============================================================================
// Slots y citas
// =============================================================================
async function getAvailableSlots(date, serviceId) {
  const data = await odooGet('/skeen/frontend/v1/appointments/slots', { date, servicio_id: serviceId });
  return data.slots || [];
}

function timeToFloat(timeStr) {
  const [h, m] = timeStr.split(':').map(Number);
  return h + m / 60;
}

async function createAppointment({ phone, name, serviceId, date, time }) {
  const patient = await findOrCreatePatient(phone, name);
  const data = await odooPost('/skeen/frontend/v1/appointments', {
    partner_id: patient.id,
    servicio_id: serviceId,
    date,
    time: timeToFloat(time),
    state: 'confirmed',
    medium: 'onsite',
  });
  return data.appointment;
}

async function getPatientAppointments(phone) {
  const patient = await findPatientByPhone(phone);
  if (!patient) return [];
  const data = await odooGet('/skeen/frontend/v1/appointments', { patient_id: patient.id });
  return data.appointments || [];
}

async function cancelAppointment(phone, appointmentId = null) {
  const patient = await findPatientByPhone(phone);
  if (!patient) throw new Error('Paciente no encontrado');
  const apps = await getPatientAppointments(phone);
  const active = apps.filter(a => ['pending', 'confirmed', 'arrived', 'in_progress'].includes(a.state));
  if (active.length === 0) throw new Error('No tienes citas activas para cancelar');
  const target = appointmentId
    ? active.find(a => a.id === parseInt(appointmentId))
    : active[0];
  if (!target) throw new Error('Cita no encontrada');
  await odooPost(`/skeen/frontend/v1/appointments/${target.id}/status`, { action: 'cancel', reason: 'Solicitud vía WhatsApp' });
  return target;
}

// =============================================================================
// Monedero y pagos
// =============================================================================
async function getWalletBalance(phone) {
  try {
    const data = await odooGet(`/skeen/frontend/v1/wallets/${encodeURIComponent(phone)}`);
    return data.wallet || { points: 0, equivalent_mxn: 0, history: [] };
  } catch (e) {
    return { points: 0, equivalent_mxn: 0, history: [] };
  }
}

async function getPendingBalance(phone) {
  const apps = await getPatientAppointments(phone);
  const pending = apps.filter(a => ['pending', 'confirmed', 'arrived', 'in_progress', 'done'].includes(a.state) && a.payment_state !== 'paid');
  return pending.map(a => ({ id: a.id, reference: a.reference, service: a.service, amount: a.price, date: a.date }));
}

// =============================================================================
// Clasificación de intención
// =============================================================================
function classifyByKeywords(text) {
  const t = text.toLowerCase();

  const intents = {
    agendar: ['agendar', 'cita', 'quiero una cita', 'necesito cita', 'hora', 'disponibilidad', 'reagendar', 'cambiar cita', 'cancelar cita', 'cancelar mi cita'],
    servicios: ['servicios', 'tratamientos', 'precios', 'catálogo', 'catalogo', 'cuanto cuesta', 'cuánto cuesta', 'costo', 'peel', 'laser', 'láser', 'acne', 'acné', 'facial', 'dermatología', 'dermatologia'],
    monedero: ['puntos', 'monedero', 'saldo', 'redimir', 'fidelidad'],
    pagos: ['pagar', 'saldo', 'deuda', 'cuanto debo', 'cuánto debo', 'pendiente', 'factura'],
    humano: ['agente', 'humano', 'especialista', 'doctor', 'doctora', 'recepcionista'],
  };

  for (const [intent, words] of Object.entries(intents)) {
    for (const word of words) {
      if (t.includes(word)) return intent;
    }
  }
  return null;
}

async function classifyIntent(text) {
  const kw = classifyByKeywords(text);
  if (kw) {
    console.log(`[Classify] keyword match: ${kw} for "${text}"`);
    return kw;
  }

  // Fallback: usar Hermes/Nan para clasificar
  const prompt = `Clasifica la intención del siguiente mensaje de un paciente de clínica dermatológica en UNA sola palabra: agendar, servicios, monedero, pagos, otro.
NO uses "humano" a menos que el usuario pida explícitamente hablar con una persona.

Mensaje: "${text}"

Intención:`;
  try {
    const result = await callNanBuilders([{ role: 'user', content: prompt }]);
    const clean = result.trim().toLowerCase().replace(/[^a-záéíóúñ]/g, '');
    console.log(`[Classify] LLM raw: "${result}" clean: "${clean}" for "${text}"`);
    if (clean.includes('agendar')) return 'agendar';
    if (clean.includes('servicio')) return 'servicios';
    if (clean.includes('monedero') || clean.includes('punto')) return 'monedero';
    if (clean.includes('pago') || clean.includes('deuda')) return 'pagos';
  } catch (e) {
    console.error('[Classify] Error:', e.message);
  }
  return 'otro';
}

// =============================================================================
// Handlers por intención
// =============================================================================
async function handleServicios(text) {
  const catalog = getCleanServices();
  if (catalog.length === 0) {
    return 'En SKEEN ofrecemos consultas dermatológicas, tratamientos faciales, peel químico y depilación láser. ¿Te gustaría agendar una consulta?';
  }

  const matched = matchServiceByText(text, await getServices());
  if (matched) {
    return `Hola! El servicio **${matched.name}** tiene un precio aproximado de **$${matched.price} MXN** y una duración de **${matched.duration_min} minutos**.

${matched.description || 'Es un tratamiento personalizado según tu tipo de piel.'}

¿Te gustaría agendar una cita para evaluarte?`;
  }

  let reply = 'Hola! En SKEEN contamos con estos servicios:\n\n';
  for (const s of catalog.slice(0, 8)) {
    reply += `• **${s.name}** — $${s.price} MXN (${s.duration_min} min)\n`;
  }
  reply += '\n¿Te gustaría agendar una consulta para recomendarte el mejor tratamiento?';
  return reply;
}

async function handleAgendar(phone, text, memory) {
  const catalog = getCleanServices();
  const services = await getServices();

  // Estado actual del flujo
  const ctx = memory.context || {};

  // Cancelar flujo
  if (text.toLowerCase().includes('cancelar') && ctx.step) {
    memory.context = {};
    saveMemory(phone, memory);
    return 'Cancelé el proceso de agendamiento. ¿En qué más puedo ayudarte?';
  }

  // Paso 1: seleccionar servicio
  if (!ctx.serviceId) {
    const matched = matchServiceByText(text, services);
    if (matched) {
      ctx.serviceId = matched.id;
      ctx.serviceName = matched.name;
      ctx.serviceDuration = matched.duration_min;
      ctx.step = 'awaiting_date';
      memory.context = ctx;
      saveMemory(phone, memory);
      return `Perfecto, elegiste **${matched.name}** (${matched.duration_min} min).\n\n¿Qué día te gustaría agendar? Puedes escribir la fecha como "mañana", "viernes", o "2026-07-15".`;
    }

    let reply = 'Claro, con gusto te ayudo a agendar. ¿Qué servicio necesitas?\n\n';
    const top = services.filter(s => s.price > 0 && s.duration_min > 0).slice(0, 6);
    for (const s of top) {
      reply += `• ${s.name} — $${s.price} MXN (${s.duration_min} min)\n`;
    }
    reply += '\nSi no sabes cuál elegir, agenda una **consulta** y te orientamos.';
    ctx.step = 'awaiting_service';
    memory.context = ctx;
    saveMemory(phone, memory);
    return reply;
  }

  // Paso 2: seleccionar fecha
  if (ctx.step === 'awaiting_date' || !ctx.date) {
    let dateStr = parseDatePhrase(text);
    if (!dateStr) {
      return 'No entendí la fecha. ¿Puedes escribirla como "mañana", "próximo lunes", o "2026-07-15"?';
    }
    ctx.date = dateStr;
    ctx.step = 'awaiting_time';
    memory.context = ctx;
    saveMemory(phone, memory);

    let slots = [];
    try {
      slots = await getAvailableSlots(dateStr, ctx.serviceId);
    } catch (e) {
      console.error('[Agendar] slots error:', e.message);
      memory.context = {};
      saveMemory(phone, memory);
      return `Tuve un problema consultando disponibilidad para ese servicio (${e.message}). Reinicié el proceso; escribe **agendar** y elige el servicio de nuevo, o escribe **AGENTE** para que te atienda recepción.`;
    }
    if (!slots.length) {
      return `No encontré disponibilidad para el **${formatDateEs(dateStr)}**. ¿Te gustaría intentar con otra fecha?`;
    }
    const top = slots.slice(0, 5);
    ctx.presentedSlots = top.map(s => s.time_str);
    let reply = `Estos son los horarios disponibles para **${ctx.serviceName}** el **${formatDateEs(dateStr)}**:\n\n`;
    top.forEach((s, i) => { reply += `${i + 1}. ${formatTimeAmPm(s.time_str)}\n`; });
    reply += '\nEscribe el número o la hora que prefieras.';
    return reply;
  }

  // Paso 3: seleccionar hora
  if (ctx.step === 'awaiting_time' || !ctx.time) {
    const slots = await getAvailableSlots(ctx.date, ctx.serviceId);
    const presented = (ctx.presentedSlots || []).map(ts => slots.find(s => s.time_str === ts)).filter(Boolean);
    const searchPool = presented.length ? presented : slots.slice(0, 5);
    let selected = null;
    const t = text.trim().toLowerCase();
    const num = parseInt(text.match(/\d+/)?.[0]);
    if (num && num >= 1 && num <= searchPool.length) {
      selected = searchPool[num - 1];
    } else {
      selected = searchPool.find(s => {
        const ts = s.time_str.trim().toLowerCase();
        return ts === t || formatTimeAmPm(s.time_str).toLowerCase() === t || ts.startsWith(t);
      });
    }
    if (!selected) {
      return 'No entendí el horario. Por favor elige una opción de la lista (escribe el número o la hora, ej. 10:00). Puedes escribir "cancelar" para salir.';
    }
    ctx.time = selected.time_str;
    ctx.step = 'awaiting_name';
    memory.context = ctx;
    saveMemory(phone, memory);
    return `Muy bien, horario seleccionado: **${formatTimeAmPm(ctx.time)}**.\n\n¿A qué nombre registramos la cita?`;
  }

  // Paso 4: confirmar nombre
  if (ctx.step === 'awaiting_name' || !ctx.patientName) {
    ctx.patientName = text.trim();
    ctx.step = 'confirm';
    memory.context = ctx;
    saveMemory(phone, memory);
    return `Confirmemos tu cita:\n\n• Servicio: ${ctx.serviceName}\n• Fecha: ${formatDateEs(ctx.date)}\n• Hora: ${formatTimeAmPm(ctx.time)}\n• Paciente: ${ctx.patientName}\n• Teléfono: ${phone}\n\n¿Confirmamos? (sí/no)`;
  }

  // Confirmar
  if (ctx.step === 'confirm') {
    if (/^(sí|si|s|yes|confirmar|ok)$/i.test(text.trim())) {
      try {
        const appointment = await createAppointment({
          phone,
          name: ctx.patientName,
          serviceId: ctx.serviceId,
          date: ctx.date,
          time: ctx.time,
        });
        memory.context = {};
        saveMemory(phone, memory);
        return `¡Perfecto! Tu cita está confirmada ✨\n\n• Servicio: ${ctx.serviceName}\n• Fecha: ${formatDateEs(appointment.date)}\n• Hora: ${formatTimeAmPm(appointment.time)}\n• Referencia: ${appointment.reference}\n\nDirección: ${CLINIC_ADDRESS}\n\nTe enviaremos un recordatorio un día antes. Si necesitas cambiar o cancelar, solo escríbenos.`;
      } catch (e) {
        return `No pude crear la cita en este momento: ${e.message}. Por favor intenta de nuevo o escribe **AGENTE** para hablar con recepción.`;
      }
    } else {
      memory.context = {};
      saveMemory(phone, memory);
      return 'Entendido, cancelé la cita. ¿En qué más puedo ayudarte?';
    }
  }

  return '¿En qué más puedo ayudarte?';
}

async function handleMonedero(phone) {
  const balance = await getWalletBalance(phone);
  if (balance.points === 0) {
    return `Tu monedero SKEEN:\n\nSaldo actual: **0 puntos**\n\nPor cada $10 MXN en compras acumulas 1 punto (= $1 MXN de descuento). ¡Te esperamos para empezar a sumar!`;
  }
  let reply = `Tu monedero SKEEN 💙\n\nSaldo actual: **${balance.points} puntos** (= $${balance.equivalent_mxn} MXN)\n`;
  if (balance.history && balance.history.length > 0) {
    reply += '\nHistorial reciente:\n';
    balance.history.slice(0, 5).forEach(h => {
      reply += `• ${h.date}: ${h.points > 0 ? '+' : ''}${h.points} pts (${h.description})\n`;
    });
  }
  reply += '\nPuedes redimir hasta el 50% del total de tu próxima compra.';
  return reply;
}

async function handlePagos(phone) {
  const pending = await getPendingBalance(phone);
  if (!pending.length) {
    return 'Genial, no tienes pagos pendientes en este momento. ¿Te gustaría agendar tu próxima visita?';
  }
  const total = pending.reduce((sum, p) => sum + p.amount, 0);
  let reply = `Este es tu saldo pendiente:\n\n`;
  pending.slice(0, 5).forEach(p => {
    reply += `• ${p.service || p.reference} — $${p.amount} MXN (fecha: ${p.date})\n`;
  });
  reply += `\n**Total pendiente: $${total} MXN**\n\nPuedes pagar en clínica o solicitar un link de pago. Escríbenos **AGENTE** si necesitas ayuda.`;
  return reply;
}

async function handleHumano() {
  return 'Te conecto con un especialista enseguida. Espera un momento 💙';
}

async function handleGeneral(text, phone, memory) {
  const systemPrompt = `Eres Sofia, la asistente virtual de SKEEN Derma Experts, una clínica dermatológica en Playas de Rosarito, BC, México.

Tu personalidad: cálida, profesional, empática y eficiente.
Idioma: Español (México).

Datos de la clínica:
- Nombre: SKEEN Derma Experts
- Dirección: ${CLINIC_ADDRESS}
- Horario: ${CLINIC_HOURS}
- Teléfono: ${CLINIC_PHONE}

Límites importantes:
- NO diagnosticar condiciones médicas.
- NO recomendar medicamentos específicos.
- SIEMPRE sugerir consulta presencial para evaluación.
- Máximo 3 párrafos por mensaje.
- Máximo 2 emojis por mensaje.

Servicios principales (puedes ofrecer agendar valoración gratuita):
- Consulta Dermatológica General — $800 MXN, 30 min
- Consulta de Valoración Gratuita — 15 min
- Tratamiento Facial para Acné — $1,500 MXN, 60 min
- Peel Químico — $1,500 MXN, 30 min
- Depilación Láser — desde $500 MXN por zona

Cuando el usuario pregunte por servicios, ofrece información general y agendar valoración.
Si quiere agendar, cita, hora o disponibilidad, indica que puede escribir "quiero agendar".
Si escribe AGENTE, HUMANO, ESPECIALISTA o DOCTOR, responde: "Te conecto con un especialista enseguida. Espera un momento 💙"`;

  const recent = (memory.history || []).slice(-6).map(h => ({ role: h.role, content: h.content }));
  const messages = [{ role: 'system', content: systemPrompt }, ...recent, { role: 'user', content: text }];
  return await callNanBuilders(messages);
}

// =============================================================================
// Fechas
// =============================================================================
function parseDatePhrase(text) {
  const t = text.toLowerCase().trim();
  const today = getTodayTz();

  // Patrón ISO
  const iso = text.match(/(\d{4})-(\d{2})-(\d{2})/);
  if (iso) return `${iso[1]}-${iso[2]}-${iso[3]}`;

  // Patrón dd/mm o dd/mm/yyyy
  const slash = text.match(/(\d{1,2})\/(\d{1,2})(?:\/(\d{4}))?/);
  if (slash) {
    const d = parseInt(slash[1]);
    const m = parseInt(slash[2]);
    const y = slash[3] ? parseInt(slash[3]) : parseInt(today.split('-')[0]);
    return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
  }

  if (t.includes('hoy')) return today;
  if (t.includes('mañana')) return addDaysToIso(today, 1);

  const days = ['domingo', 'lunes', 'martes', 'miércoles', 'miercoles', 'jueves', 'viernes', 'sábado', 'sabado'];
  for (let i = 0; i < days.length; i++) {
    if (t.includes(days[i])) {
      return getNextDayOfWeekIso(today, i);
    }
  }

  return null;
}

function addDays(date, days) {
  const r = new Date(date);
  r.setDate(r.getDate() + days);
  return r;
}

function getNextDayOfWeek(from, dayIndex) {
  const r = new Date(from);
  const diff = (dayIndex - r.getDay() + 7) % 7;
  r.setDate(r.getDate() + (diff === 0 ? 7 : diff));
  return r;
}

function formatDateIso(d) {
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}

// =============================================================================
// Webhook
// =============================================================================
app.get('/health', (req, res) => {
  res.json({ status: 'ok', service: 'skeen-hermes-webhook-bridge', version: '2.0.0' });
});

async function isConversationAssigned(conversation_id) {
  try {
    const data = await odooGet(`/skeen/frontend/v1/wacrm/conversations/${encodeURIComponent(conversation_id)}/assigned`);
    return data.assigned === true;
  } catch (e) {
    console.error('[Bridge] Error consultando asignación:', e.message);
    return false;
  }
}

app.post('/send-message', async (req, res) => {
  try {
    const { conversation_id, text, assigned_agent_id } = req.body || {};
    if (!conversation_id || !text) {
      return res.status(400).json({ status: 'error', message: 'Faltan conversation_id o text' });
    }

    // Resolver teléfono desde Odoo
    const convData = await odooGet(`/skeen/frontend/v1/wacrm/conversations`, {});
    const conversation = convData.conversations?.find(c => c.external_id === conversation_id);
    if (!conversation || !conversation.contact_phone) {
      return res.status(404).json({ status: 'error', message: 'Conversación no encontrada' });
    }

    const to = normalizePhone(conversation.contact_phone);
    const result = await sendWacrmMessageWithFallback(to, text);

    // Asignar conversación al agente
    if (assigned_agent_id) {
      try {
        await odooPost(`/skeen/frontend/v1/wacrm/conversations/${encodeURIComponent(conversation_id)}/assign`, {
          assigned_agent_id,
        });
      } catch (assignErr) {
        console.warn('[Bridge] No se pudo asignar conversación:', assignErr.message);
      }
    }

    res.json({ status: 'success', message: 'Mensaje enviado', result });
  } catch (error) {
    console.error('[Bridge] Error en /send-message:', error);
    res.status(500).json({ status: 'error', message: error.message });
  }
});

app.post('/webhook/wacrm', async (req, res) => {
  try {
    const payload = req.body;
    const eventType = payload.event;
    const data = payload.data || {};

    console.log(`[WACRM] Webhook recibido: ${eventType}`, JSON.stringify(data).substring(0, 200));

    // Solo procesar mensajes entrantes del cliente
    if (eventType !== 'message.received') {
      return res.json({ status: 'ignored', event: eventType });
    }

    const from = normalizePhone(data.contact_phone || data.from);
    const text = data.text || '';
    const conversation_id = data.conversation_id;
    const contact_id = data.contact_id;
    const contactName = data.contact_name || data.name;

    if (!from || !text) {
      return res.status(400).json({ error: 'Missing from or text', received: payload });
    }

    // Si la conversación está asignada a un agente humano, no responder
    if (conversation_id && await isConversationAssigned(conversation_id)) {
      console.log(`[WACRM] Conversación ${conversation_id} asignada a agente. Saltando respuesta automática.`);
      return res.json({ status: 'ignored', reason: 'assigned_to_agent', conversation_id });
    }

    const memory = loadMemory(from);
    memory.history.push({ role: 'user', content: text, ts: new Date().toISOString() });
    if (memory.history.length > 20) memory.history = memory.history.slice(-20);

    // Handoff a humano
    if (/\b(agente|humano|especialista|doctor|doctora|recepcionista)\b/i.test(text)) {
      const reply = await handleHumano();
      await sendWacrmMessageWithFallback(from, reply);
      memory.history.push({ role: 'assistant', content: reply, ts: new Date().toISOString() });
      saveMemory(from, memory);
      return res.json({ status: 'success', reply, handoff: true, from, conversation_id, contact_id });
    }

    // Si hay un flujo activo (ej. agendamiento), continuarlo sin re-clasificar
    let intent;
    if (memory.context && memory.context.step) {
      intent = 'agendar';
      console.log(`[Context] continuando flujo ${memory.context.step} para ${from}`);
    } else {
      intent = await classifyIntent(text);
    }
    console.log(`[Intent] ${from} -> ${intent}`);

    let reply;
    switch (intent) {
      case 'servicios':
        reply = await handleServicios(text);
        break;
      case 'agendar':
        reply = await handleAgendar(from, text, memory);
        break;
      case 'monedero':
        reply = await handleMonedero(from);
        break;
      case 'pagos':
        reply = await handlePagos(from);
        break;
      case 'humano':
        reply = await handleHumano();
        break;
      default:
        reply = await handleGeneral(text, from, memory);
    }

    await sendWacrmMessageWithFallback(from, reply);

    memory.history.push({ role: 'assistant', content: reply, ts: new Date().toISOString() });
    saveMemory(from, memory);

    res.json({ status: 'success', reply, intent, from, conversation_id, contact_id });
  } catch (error) {
    console.error('Error procesando webhook:', error);
    res.status(500).json({ status: 'error', message: error.message });
  }
});

// =============================================================================
// Recordatorios automáticos
// =============================================================================
const REMINDERS_SENT_FILE = path.join(MEMORY_DIR, 'reminders_sent.json');

function loadRemindersSent() {
  try {
    if (fs.existsSync(REMINDERS_SENT_FILE)) {
      return JSON.parse(fs.readFileSync(REMINDERS_SENT_FILE, 'utf8'));
    }
  } catch (e) {
    console.error('[Reminders] Error cargando registro:', e.message);
  }
  return {};
}

function saveRemindersSent(sent) {
  try {
    fs.writeFileSync(REMINDERS_SENT_FILE, JSON.stringify(sent, null, 2));
  } catch (e) {
    console.error('[Reminders] Error guardando registro:', e.message);
  }
}

function reminderKey(type, phone, dateStr) {
  const safePhone = (phone || '').replace(/\D/g, '');
  return `${type}:${safePhone}:${dateStr}`;
}

function formatDateEsShort(dateStr) {
  const [y, m, d] = dateStr.split('-').map(Number);
  const dt = new Date(Date.UTC(y, m - 1, d, 7, 0, 0));
  return dt.toLocaleDateString('es-MX', { weekday: 'long', month: 'long', day: 'numeric', timeZone: 'America/Tijuana' });
}

async function runReminders() {
  if (process.env.REMINDERS_ENABLED !== 'true') {
    console.log('[Reminders] Desactivados (REMINDERS_ENABLED!=true). No se envían recordatorios automáticos.');
    return;
  }
  console.log('[Reminders] Ejecutando recordatorios automáticos...');
  try {
    const data = await odooGet('/skeen/frontend/v1/reminders/pending');
    if (data.status !== 'success') {
      console.error('[Reminders] Error consultando Odoo:', data.message);
      return;
    }

    const sent = loadRemindersSent();
    const today = new Date().toISOString().split('T')[0];

    // Recordatorios 24h antes
    for (const r of data.reminders || []) {
      const key = reminderKey('reminder', r.phone, r.date);
      if (sent[key] === today) continue;
      const phone = normalizePhone(r.phone);
      const text = `Hola ${r.patient_name || ''}! Te recordamos tu cita mañana ${formatDateEsShort(r.date)} a las ${formatTimeAmPm(r.time)} en SKEEN Derma Experts.`;
      try {
        await sendWacrmMessageWithFallback(phone, text);
        sent[key] = today;
        console.log(`[Reminders] Recordatorio enviado a ${phone}`);
      } catch (e) {
        console.error(`[Reminders] Error enviando a ${phone}:`, e.message);
      }
    }

    // Follow-ups 7 días después
    for (const f of data.followups || []) {
      const key = reminderKey('followup', f.phone, f.date);
      if (sent[key] === today) continue;
      const phone = normalizePhone(f.phone);
      const text = `Hola ${f.patient_name || ''}! Han pasado 7 días desde tu ${f.service || 'visita'}. ¿Cómo te sientes? Estamos aquí para cualquier duda.`;
      try {
        await sendWacrmMessageWithFallback(phone, text);
        sent[key] = today;
        console.log(`[Reminders] Follow-up enviado a ${phone}`);
      } catch (e) {
        console.error(`[Reminders] Error enviando follow-up a ${phone}:`, e.message);
      }
    }

    // Re-engagement 90 días (MARKETING — requiere template aprobado + opt-in)
    if (process.env.REENGAGE_ENABLED !== 'true') {
      if ((data.reengagements || []).length) {
        console.log(`[Reminders] Re-engagement desactivado (REENGAGE_ENABLED!=true). ${(data.reengagements || []).length} candidatos omitidos.`);
      }
    } else for (const r of data.reengagements || []) {
      const key = reminderKey('reengagement', r.phone, today);
      if (sent[key] === today) continue;
      const phone = normalizePhone(r.phone);
      const text = `Hola ${r.patient_name || ''}! Te extrañamos en SKEEN. Agenda tu próxima visita y recibe 50 puntos de regalo.`;
      try {
        await sendWacrmMessageWithFallback(phone, text);
        sent[key] = today;
        console.log(`[Reminders] Re-engagement enviado a ${phone}`);
      } catch (e) {
        console.error(`[Reminders] Error enviando re-engagement a ${phone}:`, e.message);
      }
    }

    saveRemindersSent(sent);
    console.log('[Reminders] Ejecución completada.');
  } catch (error) {
    console.error('[Reminders] Error general:', error);
  }
}

app.post('/trigger-reminders', async (req, res) => {
  try {
    await runReminders();
    res.json({ status: 'success', message: 'Recordatorios ejecutados' });
  } catch (error) {
    res.status(500).json({ status: 'error', message: error.message });
  }
});

const PORT = process.env.PORT || 8090;
app.listen(PORT, '0.0.0.0', () => {
  console.log(`SKEEN Hermes Webhook Bridge v2 escuchando en http://0.0.0.0:${PORT}`);
  console.log(`Health: http://localhost:${PORT}/health`);
  console.log(`Webhook: http://localhost:${PORT}/webhook/wacrm`);
  console.log(`Send message: http://localhost:${PORT}/send-message`);
  console.log(`Trigger reminders: http://localhost:${PORT}/trigger-reminders`);
  console.log(`Hermes API: ${HERMES_API_URL}`);
  console.log(`Odoo URL: ${ODOO_URL}`);
  console.log(`WACRM URL: ${WACRM_URL}`);

  // Heartbeat: recordatorios cada 30 minutos (desactivado por defecto).
  // REGLA DEL CLIENTE: los recordatorios/re-engagement permanecen DESACTIVADOS
  // hasta que se cambie al número de WhatsApp de SKEEN. No exportar
  // REMINDERS_ENABLED=true ni REENGAGE_ENABLED=true antes de ese cambio,
  // aunque ya existan templates de Meta aprobados. Revisar también el gate
  // en runReminders() (línea ~897).
  const REMINDER_INTERVAL = parseInt(process.env.REMINDER_INTERVAL_MS, 10) || 30 * 60 * 1000;
  if (process.env.REMINDERS_ENABLED === 'true') {
    setInterval(runReminders, REMINDER_INTERVAL);
    console.log(`[Reminders] Heartbeat configurado cada ${REMINDER_INTERVAL / 60000} minutos`);
  } else {
    console.log('[Reminders] Heartbeat DESACTIVADO (REMINDERS_ENABLED!=true). Plantillas Meta y opt-in pendientes.');
  }
});
