import { WASocket } from "@whiskeysockets/baileys";
import {
  getOrCreateConversation,
  insertMessage,
  getConversationById,
  getRecentHistory,
  getSetting,
  getMessageCount,
} from "../db";
import { generateReply } from "../ai";
import { handleCommand } from "../commands";
import { dispatchWebhook } from "../webhooks";
import { extractAndSaveLead } from "../leads-extractor";


/* ── Message Unwrapper ─────────────────────────────────────────────────── */
function getRealMessage(message: any): any {
  if (!message) return null;
  if (message.ephemeralMessage?.message) return getRealMessage(message.ephemeralMessage.message);
  if (message.viewOnceMessage?.message) return getRealMessage(message.viewOnceMessage.message);
  if (message.viewOnceMessageV2?.message) return getRealMessage(message.viewOnceMessageV2.message);
  if (message.documentWithCaptionMessage?.message) return getRealMessage(message.documentWithCaptionMessage.message);
  if (message.editedMessage?.message) return getRealMessage(message.editedMessage.message);
  if (message.deviceSentMessage?.message) return getRealMessage(message.deviceSentMessage.message);
  return message;
}

/* ── Business Hours Check ──────────────────────────────────────────────── */
function isWithinBusinessHours(): boolean {
  const enabled = getSetting("business_hours_enabled", "false") === "true";
  if (!enabled) return true; // If not configured, always available

  const startStr = getSetting("business_hours_start", "09:00");
  const endStr = getSetting("business_hours_end", "18:00");
  const daysStr = getSetting("business_days", "[1,2,3,4,5]");

  let activeDays: number[] = [1, 2, 3, 4, 5];
  try { activeDays = JSON.parse(daysStr); } catch {}

  const now = new Date();
  const currentDay = now.getDay(); // 0=Sun, 1=Mon...
  const [startH, startM] = startStr.split(":").map(Number);
  const [endH, endM] = endStr.split(":").map(Number);
  const currentMinutes = now.getHours() * 60 + now.getMinutes();
  const startMinutes = (startH || 9) * 60 + (startM || 0);
  const endMinutes = (endH || 18) * 60 + (endM || 0);

  return activeDays.includes(currentDay) && currentMinutes >= startMinutes && currentMinutes < endMinutes;
}

/* ── Auto-Reply Matching ───────────────────────────────────────────────── */
interface AutoReply {
  id: string;
  keyword: string;
  text: string;
  matchType?: "contains" | "starts_with" | "exact";
  enabled?: boolean;
}

function findAutoReply(text: string, autoReplies: AutoReply[]): string | null {
  const textLower = text.toLowerCase().trim();
  for (const rule of autoReplies) {
    if (rule.enabled === false) continue; // Skip disabled rules
    const keyword = (rule.keyword || "").toLowerCase().trim();
    if (!keyword) continue;

    let matched = false;
    switch (rule.matchType) {
      case "starts_with":
        matched = textLower.startsWith(keyword);
        break;
      case "exact":
        matched = textLower === keyword;
        break;
      default: // "contains"
        matched = textLower.includes(keyword);
    }
    if (matched) return rule.text;
  }
  return null;
}

/* ── Main Handler ──────────────────────────────────────────────────────── */
export function handleIncomingMessages(sock: WASocket) {
  sock.ev.on("messages.upsert", async (m) => {
    console.log(`[bot] Evento messages.upsert (tipo: ${m.type}, mensajes: ${m.messages?.length})`);
    if (m.type !== "notify") return;

    for (const msg of m.messages) {
      console.log(`[bot] Analizando: remoteJid=${msg.key.remoteJid}, fromMe=${msg.key.fromMe}`);

      // Skip own messages
      if (msg.key.fromMe) {
        console.log(`[bot] Ignorado: enviado por mí.`);
        continue;
      }

      const remoteJid = msg.key.remoteJid;
      if (!remoteJid) {
        console.log(`[bot] Ignorado: sin remoteJid.`);
        continue;
      }

      // Skip groups
      if (remoteJid.endsWith("@g.us")) {
        console.log(`[bot] Ignorado: es grupo.`);
        continue;
      }

      // Accept @s.whatsapp.net and @lid (newer WhatsApp protocol)
      const isIndividual = remoteJid.endsWith("@s.whatsapp.net") || remoteJid.endsWith("@lid");
      if (!isIndividual) {
        console.log(`[bot] Ignorado: JID no reconocido (${remoteJid}).`);
        continue;
      }

      const messageContent = getRealMessage(msg.message);
      if (!messageContent) {
        console.log(`[bot] Ignorado: contenido vacío.`);
        continue;
      }

      // ── Detectar tipo de mensaje y extraer texto ───────────────────────────
      const mediaType =
        messageContent.imageMessage ? "image" :
        messageContent.videoMessage ? "video" :
        messageContent.audioMessage ? "audio" :
        messageContent.documentMessage ? "document" :
        messageContent.stickerMessage ? "sticker" :
        null;

      const text =
        messageContent.conversation ||
        messageContent.extendedTextMessage?.text ||
        messageContent.imageMessage?.caption ||
        messageContent.videoMessage?.caption ||
        messageContent.documentMessage?.caption ||
        "";

      // ── Fase 8: Manejo de media sin texto ───────────────────────────────────
      // Si el mensaje es multimedia pero sin caption, registrarlo y notificar
      if (!text.trim()) {
        if (mediaType && mediaType !== "sticker") {
          console.log(`[bot] Media recibida (${mediaType}) sin caption de ${remoteJid}. Notificando...`);
          const convoMedia = getOrCreateConversation(remoteJid, msg.pushName || "Cliente");
          const mediaNote = `[${mediaType.toUpperCase()}]`;
          insertMessage(convoMedia.id, "user", mediaNote);
          dispatchWebhook("message.received", {
            phone: remoteJid,
            name: msg.pushName || null,
            type: mediaType,
            text: null,
            conversation_id: convoMedia.id,
          }).catch(() => {});
        } else {
          console.log(`[bot] Ignorado: sin texto. Keys:`, Object.keys(messageContent));
        }
        continue;
      }

      const pushName = msg.pushName || "Cliente";
      console.log(`[bot] ← Mensaje de ${pushName} (${remoteJid}): "${text}"`);

      // ── Get or create conversation ──
      const convo = getOrCreateConversation(remoteJid, pushName);
      const prevMessageCount = getMessageCount(convo.id);
      insertMessage(convo.id, "user", text);
      const isFirstContact = prevMessageCount === 0;

      // ── Fase 3: Dispatch webhook en cada mensaje entrante ──────────────────
      dispatchWebhook("message.received", {
        phone: remoteJid,
        name: pushName,
        type: mediaType ?? "text",
        text,
        conversation_id: convo.id,
        is_first_contact: isFirstContact,
      }).catch(() => {});

      // ── Fase 6: Reacción emoji como acuse de recibo instantáneo ───────────
      // Muestra un emoji al remitente para que sepa que el mensaje fue recibido
      const reactionEnabled = getSetting("reaction_enabled", "true") === "true";
      if (reactionEnabled && msg.key.id) {
        const emoji = getSetting("reaction_emoji", "👀");
        sock.sendMessage(remoteJid, {
          react: { text: emoji, key: msg.key }
        }).catch(() => {});
      }

      // ── Commands (/ticket, /estado, /mistickets, /ayuda, /cancelar) ──
      // Activar con la setting 'commands_enabled = true' en la base de datos
      const commandsEnabled = getSetting('commands_enabled', 'false') === 'true';
      if (commandsEnabled) {
        const cmdResult = await handleCommand(remoteJid, pushName, text);
        if (cmdResult.handled) {
          console.log(`[bot] Comando procesado para ${remoteJid}: "${text}"`);
          insertMessage(convo.id, 'assistant', cmdResult.reply);
          await sock.sendMessage(remoteJid, { text: cmdResult.reply });
          continue;
        }
      }

      // ── Welcome message (se envía en el primer contacto) ──
      const welcomeEnabled = getSetting('welcome_enabled', 'false') === 'true';
      const welcomeMessage = getSetting('welcome_message', '');
      if (isFirstContact && welcomeEnabled && welcomeMessage.trim()) {
        console.log(`[bot] Enviando bienvenida a ${remoteJid}`);
        insertMessage(convo.id, 'assistant', welcomeMessage);
        await sock.sendMessage(remoteJid, { text: welcomeMessage });
        // No usamos 'continue' para que el bot también procese el mensaje del usuario
        // Solo skip si el mensaje fue literalmente solo un saludo sin contenido real
        if (text.trim().split(/\s+/).length <= 2) {
          continue; // Primer mensaje muy corto ("hola", "buenos días") → solo bienvenida
        }
      }

      // ── Check mode — if HUMAN, human agent handles it via outbox ──
      const freshConvo = getConversationById(convo.id);
      if (!freshConvo || freshConvo.mode !== "AI") {
        console.log(`[bot] Modo HUMAN — esperando respuesta del agente para ${remoteJid}.`);
        continue;
      }

      // ── Business hours check (only in AI mode) ──
      if (!isWithinBusinessHours()) {
        const awayMessage = getSetting("away_message", "Estamos fuera del horario de atención. Te responderemos pronto 🙏");
        console.log(`[bot] Fuera de horario — enviando away message a ${remoteJid}`);
        insertMessage(convo.id, "assistant", awayMessage);
        await sock.sendMessage(remoteJid, { text: awayMessage });
        continue;
      }

      // ── Auto-reply by keyword ──
      const autoRepliesStr = getSetting("auto_replies", "[]");
      let autoReplies: AutoReply[] = [];
      try { autoReplies = JSON.parse(autoRepliesStr); } catch {}

      const matchedReply = findAutoReply(text, autoReplies);
      if (matchedReply) {
        const replyText = matchedReply.replace(/\{nombre\}/gi, pushName);
        console.log(`[bot] Auto-respuesta para "${text}": "${replyText}"`);
        insertMessage(convo.id, "assistant", replyText);
        await sock.sendMessage(remoteJid, { text: replyText });
        continue;
      }

      // ── AI reply ──
      try {
        console.log(`[bot] Llamando LLM para ${remoteJid}...`);
        
        // Mostrar que el bot está "escribiendo..."
        await sock.sendPresenceUpdate('composing', remoteJid);

        // Retraso opcional configurado en la base de datos (segundos)
        const delaySecondsStr = getSetting("bot_response_delay", "0");
        const delayMs = (parseInt(delaySecondsStr) || 0) * 1000;
        if (delayMs > 0) {
          console.log(`[bot] Esperando ${delayMs}ms antes de responder a ${remoteJid}...`);
          await new Promise(resolve => setTimeout(resolve, delayMs));
        }

        const t0 = Date.now();
        const history = getRecentHistory(convo.id, 20);
        const reply = await generateReply(convo.id, history);
        const duration = Date.now() - t0;

        if (reply) {
          console.log(`[bot] LLM respondió en ${duration}ms: "${reply}"`);
          insertMessage(convo.id, "assistant", reply);
          await sock.sendMessage(remoteJid, { text: reply });
          
          // Extraer prospecto en segundo plano de forma asíncrona
          extractAndSaveLead(convo.id, remoteJid).catch((err) => {
            console.error("[bot] Error ejecutando extractor de leads:", err);
          });
        }
        
        // Ocultar estado "escribiendo..."
        await sock.sendPresenceUpdate('paused', remoteJid);
      } catch (err: any) {
        console.error(`[bot] Error LLM para ${remoteJid}:`, err?.message || err);
        await sock.sendPresenceUpdate('paused', remoteJid);
      }
    }
  });
}
