import type { Database } from 'bun:sqlite';
import { ensureUserExists } from '../db';
import { normalizeWhatsAppId, isGroupId } from '../utils/whatsapp';
import { IdentityService } from '../services/identity';
import { AllowedGroups } from '../services/allowed-groups';
import { GroupSyncService } from '../services/group-sync';
import { ResponseQueue } from '../services/response-queue';
import { AdminService } from '../services/admin';
import { CommandService } from '../services/command';
import { TaskService } from '../tasks/service';
import { RateLimiter } from '../services/rate-limit';
import { Metrics } from '../services/metrics';

function getMessageText(message: any): string {
  if (!message || typeof message !== 'object') return '';
  const text =
    message.conversation ||
    message?.extendedTextMessage?.text ||
    message?.imageMessage?.caption ||
    message?.videoMessage?.caption ||
    '';
  return typeof text === 'string' ? text.trim() : '';
}

export async function handleMessageUpsert(data: any, db: Database): Promise<void> {
  if (!data?.key?.remoteJid || !data.message) {
    if (process.env.NODE_ENV !== 'test') {
      console.log('⚠️ Invalid message format - missing required fields');
      console.log(data);
    }
    return;
  }

  const messageText = getMessageText(data.message);
  if (!messageText) {
    if (process.env.NODE_ENV !== 'test') {
      console.log('⚠️ Empty or unsupported message content');
    }
    return;
  }

  // Determine sender depending on context (group vs DM) and ignore non-user messages
  const remoteJid = data.key.remoteJid;
  const participant = data.key.participant;
  const fromMe = !!data.key.fromMe;

  // Ignore broadcasts/status
  if (remoteJid === 'status@broadcast' || (typeof remoteJid === 'string' && remoteJid.endsWith('@broadcast'))) {
    if (process.env.NODE_ENV !== 'test') {
      console.log('ℹ️ Ignoring broadcast/status message');
    }
    return;
  }

  // Ignore our own messages
  if (fromMe) {
    if (process.env.NODE_ENV !== 'test') {
      console.log('ℹ️ Ignoring message sent by the bot (fromMe=true)');
    }
    return;
  }

  // Compute sender JID based on chat type (prefer participantAlt when available due to Baileys change)
  const senderRaw = isGroupId(remoteJid)
    ? (data.key.participantAlt || participant)
    : remoteJid;

  // Aprender mapping alias→número cuando vienen ambos y difieren (participant vs participantAlt)
  if (isGroupId(remoteJid)) {
    const pAlt = typeof data.key.participantAlt === 'string' ? data.key.participantAlt : null;
    const p = typeof participant === 'string' ? participant : null;
    if (pAlt && p) {
      try {
        const nAlt = normalizeWhatsAppId(pAlt);
        const n = normalizeWhatsAppId(p);
        if (process.env.NODE_ENV !== 'test') {
          console.log('[A0] message.key participants', {
            participant: p,
            participantAlt: pAlt,
            normalized_participant: n,
            normalized_participantAlt: nAlt,
            alias_upsert: !!(nAlt && n && nAlt !== n)
          });
        }
        if (nAlt && n && nAlt !== n) {
          IdentityService.upsertAlias(p, pAlt, 'message.key');
        }
      } catch {}
    }
  }

  // Normalize sender ID for consistency and validation
  const normalizedSenderId = normalizeWhatsAppId(senderRaw);
  if (!normalizedSenderId) {
    if (process.env.NODE_ENV !== 'test') {
      console.debug('⚠️ Invalid sender ID, ignoring message', { remoteJid, participant, fromMe });
    }
    return;
  }

  // Avoid processing messages from the bot number
  if (process.env.CHATBOT_PHONE_NUMBER && normalizedSenderId === process.env.CHATBOT_PHONE_NUMBER) {
    if (process.env.NODE_ENV !== 'test') {
      console.log('ℹ️ Ignoring message from the bot number');
    }
    return;
  }

  // Ensure user exists in database (swallow DB errors to keep webhook 200)
  let userId: string | null = null;
  try {
    userId = ensureUserExists(senderRaw, db);
  } catch (e) {
    if (process.env.NODE_ENV !== 'test') {
      console.error('⚠️ Error ensuring user exists, ignoring message:', e);
    }
    return;
  }
  if (!userId) {
    if (process.env.NODE_ENV !== 'test') {
      console.log('⚠️ Failed to ensure user exists, ignoring message');
    }
    return;
  }

  const messageTextTrimmed = messageText.trim();
  const isAdminCmd = messageTextTrimmed.startsWith('/admin');

  // A4: Primer DM "activar" — alta/confirmación idempotente (solo en DM)
  if (!isGroupId(remoteJid) && messageTextTrimmed === 'activar') {
    const base = (process.env.WEB_BASE_URL || '').trim();
    const msg = base
      ? "Listo, ya puedes reclamar/ser responsable y acceder a la web. Para acceder a la web, envía '/t web' y abre el enlace."
      : "Listo, ya puedes reclamar/ser responsable.";
    try {
      await ResponseQueue.add([{ recipient: normalizedSenderId, message: msg }]);
    } catch {}
    return;
  }

  // Etapa 2: Descubrimiento seguro de grupos (modo 'discover')
  if (isGroupId(remoteJid)) {
    const gatingMode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase();
    if (gatingMode === 'discover') {
      try {
        const exists = db
          .prepare(`SELECT 1 FROM allowed_groups WHERE group_id = ? LIMIT 1`)
          .get(remoteJid);
        if (!exists) {
          try { await GroupSyncService.ensureGroupLabelAndName(remoteJid); } catch {}
          try { AllowedGroups.upsertPending(remoteJid, (GroupSyncService.activeGroupsCache.get(remoteJid) || null), normalizedSenderId); } catch {}
          try { Metrics.inc('unknown_groups_discovered_total'); } catch {}
          try {
            const notify = String(process.env.NOTIFY_ADMINS_ON_DISCOVERY || 'false').toLowerCase() === 'true';
            if (notify && !isAdminCmd) {
              const admins = AdminService.getAdmins();
              if (admins.length > 0) {
                const info = remoteJid;
                const msg = `🔎 Nuevo grupo detectado: ${info}\nEstado: pending.\nUsa /admin habilitar-aquí desde el grupo o /admin allow-group ${info}.`;
                await ResponseQueue.add(admins.map(a => ({ recipient: a, message: msg })));
              }
            }
          } catch {}
          if (!isAdminCmd) return;
        }
      } catch {
        // Si la tabla no existe por alguna razón, intentar upsert y retornar igualmente
        try { await GroupSyncService.ensureGroupLabelAndName(remoteJid); } catch {}
        try { AllowedGroups.upsertPending(remoteJid, (GroupSyncService.activeGroupsCache.get(remoteJid) || null), normalizedSenderId); } catch {}
        try { Metrics.inc('unknown_groups_discovered_total'); } catch {}
        try {
          const notify = String(process.env.NOTIFY_ADMINS_ON_DISCOVERY || 'false').toLowerCase() === 'true';
          if (notify && !isAdminCmd) {
            const admins = AdminService.getAdmins();
            if (admins.length > 0) {
              const info = remoteJid;
              const msg = `🔎 Nuevo grupo detectado: ${info}\nEstado: pending.\nUsa /admin habilitar-aquí desde el grupo o /admin allow-group ${info}.`;
              await ResponseQueue.add(admins.map(a => ({ recipient: a, message: msg })));
            }
          }
        } catch {}
        if (!isAdminCmd) return;
      }
    }
  }

  // Etapa 3: Gating en modo 'enforce' — ignorar mensajes de grupos no permitidos
  if (isGroupId(remoteJid)) {
    const gatingMode2 = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase();
    if (gatingMode2 === 'enforce') {
      try {
        const allowed = AllowedGroups.isAllowed(remoteJid);
        if (!allowed && !isAdminCmd) {
          try { Metrics.inc('messages_blocked_group_total'); } catch {}
          return;
        }
      } catch {
        // Si falla el check por cualquier motivo, ser conservadores y permitir
      }
    }
  }

  // Manejo de comandos de administración (/admin) antes de cualquier otra lógica de grupo
  if (messageTextTrimmed.startsWith('/admin')) {
    const adminResponses = await AdminService.handle({
      sender: normalizedSenderId,
      groupId: remoteJid,
      message: messageText
    });
    if (adminResponses.length > 0) {
      await ResponseQueue.add(adminResponses);
    }
    return;
  }

  // Check/ensure group exists (allow DMs always)
  if (isGroupId(data.key.remoteJid) && !GroupSyncService.isGroupActive(data.key.remoteJid)) {
    // En tests, mantener comportamiento anterior: ignorar mensajes de grupos inactivos
    if (process.env.NODE_ENV === 'test') {
      return;
    }
    if (process.env.NODE_ENV !== 'test') {
      console.log('ℹ️ Group not active in cache — ensuring group (no immediate members sync)');
    }
    try {
      GroupSyncService.ensureGroupExists(data.key.remoteJid);
      try { GroupSyncService.upsertMemberSeen(data.key.remoteJid, normalizedSenderId); } catch {}
    } catch (e) {
      if (process.env.NODE_ENV !== 'test') {
        console.error('⚠️ Failed to ensure group on-the-fly:', e);
      }
    }
  }

  // Forward to command service only if it's a text-ish message and starts with /t or /tarea
  if (messageTextTrimmed.startsWith('/tarea') || messageTextTrimmed.startsWith('/t')) {
    // Rate limiting básico por usuario (desactivado en tests)
    if (process.env.NODE_ENV !== 'test') {
      const allowed = RateLimiter.checkAndConsume(normalizedSenderId);
      if (!allowed) {
        // Notificar como máximo una vez por minuto
        if (RateLimiter.shouldNotify(normalizedSenderId)) {
          await ResponseQueue.add([{
            recipient: normalizedSenderId,
            message: `Has superado el límite de ${((() => { const v = Number(process.env.RATE_LIMIT_PER_MIN); return Number.isFinite(v) && v > 0 ? v : 15; })())} comandos por minuto. Inténtalo de nuevo en un momento.`
          }]);
        }
        return;
      }
    }
    // Extraer menciones desde el mensaje (varios formatos)
    const mentions = data.message?.contextInfo?.mentionedJid
      || data.message?.extendedTextMessage?.contextInfo?.mentionedJid
      || data.message?.imageMessage?.contextInfo?.mentionedJid
      || data.message?.videoMessage?.contextInfo?.mentionedJid
      || [];

    // Asegurar que CommandService y TaskService usen la misma DB (tests/producción)

    // Delegar el manejo del comando
    const messageId = typeof data?.key?.id === 'string' ? data.key.id : null;
    const participantForKey = typeof data?.key?.participantAlt === 'string'
      ? data.key.participantAlt
      : (typeof data?.key?.participant === 'string' ? data.key.participant : null);
    const outcome = await CommandService.handleWithOutcome({
      sender: normalizedSenderId,
      groupId: data.key.remoteJid,
      message: messageText,
      mentions,
      messageId: messageId || undefined,
      participant: participantForKey || undefined,
      fromMe: !!data?.key?.fromMe
    });
    const responses = outcome.responses;

    // Encolar respuestas si las hay
    if (responses.length > 0) {
      await ResponseQueue.add(responses);
    }

    // Reaccionar al mensaje del comando con outcome explícito
    try {
      const reactionsEnabled = String(process.env.REACTIONS_ENABLED || 'false').toLowerCase();
      const enabled = ['true','1','yes','on'].includes(reactionsEnabled);
      if (!enabled) return;

      if (!messageId) return;

      const scope = String(process.env.REACTIONS_SCOPE || 'groups').toLowerCase();
      const isGroup = isGroupId(data.key.remoteJid);
      if (scope !== 'all' && !isGroup) return;

      // Respetar gating 'enforce'
      const mode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase();
      if (mode === 'enforce' && isGroup) {
        try {
          if (!AllowedGroups.isAllowed(data.key.remoteJid)) {
            return;
          }
        } catch {}
      }

      const emoji = outcome.ok ? '🤖' : '⚠️';
      const participant = typeof data?.key?.participantAlt === 'string'
        ? data.key.participantAlt
        : (typeof data?.key?.participant === 'string' ? data.key.participant : undefined);
      await ResponseQueue.enqueueReaction(data.key.remoteJid, messageId, emoji, { participant, fromMe: !!data?.key?.fromMe });
    } catch (e) {
      // No romper el flujo por errores de reacción
      if (process.env.NODE_ENV !== 'test') {
        console.warn('⚠️ Reaction enqueue failed:', e);
      }
    }
  }
}
