import type { Database } from 'bun:sqlite';
import { ensureUserExists } from '../../../db';
import { normalizeWhatsAppId, isGroupId } from '../../../utils/whatsapp';
import { TaskService } from '../../../tasks/service';
import { GroupSyncService } from '../../group-sync';
import { ContactsService } from '../../contacts';
import { IdentityService } from '../../identity';
import { Metrics } from '../../metrics';
import { ICONS } from '../../../utils/icons';
import { codeId, formatDDMM } from '../../../utils/formatting';
import { parseNueva } from '../parsers/nueva';
import { CTA_HELP } from '../shared';
import { buildJitAssigneePrompt, maybeEnqueueOnboardingBundle } from '../../onboarding';

type Ctx = {
  sender: string;
  groupId: string;
  message: string;
  mentions: string[];
  messageId?: string;
  participant?: string;
  fromMe?: boolean;
};

type Msg = {
  recipient: string;
  message: string;
  mentions?: string[];
};


export async function handleNueva(context: Ctx, deps: { db: Database }): Promise<Msg[]> {
  const tokens = (context.message || '').trim().split(/\s+/);

  // Normalizar menciones del contexto para parseo y asignaciones (A2: fallback a números plausibles)
  const MIN_FALLBACK_DIGITS = (() => {
    const raw = (process.env.ONBOARDING_FALLBACK_MIN_DIGITS || '').trim();
    const n = parseInt(raw || '8', 10);
    return Number.isFinite(n) && n > 0 ? n : 8;
  })();
  const MAX_FALLBACK_DIGITS = (() => {
    const raw = (process.env.ONBOARDING_FALLBACK_MAX_DIGITS || '').trim();
    const n = parseInt(raw || '15', 10);
    return Number.isFinite(n) && n > 0 ? n : 15;
  })();

  type FailReason = 'non_numeric' | 'too_short' | 'too_long' | 'from_lid' | 'invalid';
  const isDigits = (s: string) => /^\d+$/.test(s);
  const plausibility = (s: string, opts?: { fromLid?: boolean }): { ok: boolean; reason?: FailReason } => {
    if (!s) return { ok: false, reason: 'invalid' };
    if (opts?.fromLid) return { ok: false, reason: 'from_lid' };
    if (!isDigits(s)) return { ok: false, reason: 'non_numeric' };
    if (s.length < MIN_FALLBACK_DIGITS) return { ok: false, reason: 'too_short' };
    if (s.length >= MAX_FALLBACK_DIGITS) return { ok: false, reason: 'too_long' };
    return { ok: true };
  };
  const incOnboardingFailure = (source: 'mentions' | 'tokens', reason: FailReason) => {
    try {
      const gid = isGroupId(context.groupId) ? context.groupId : 'dm';
      Metrics.inc('onboarding_assign_failures_total', 1, { group_id: String(gid), source, reason });
    } catch { }
  };

  // 1) Menciones aportadas por el backend (JIDs crudos)
  const unresolvedAssigneeDisplays: string[] = [];
  const mentionsNormalizedFromContext = Array.from(new Set(
    (context.mentions || []).map((j) => {
      const norm = normalizeWhatsAppId(j);
      if (!norm) {
        // agregar a no resolubles para JIT (mostrar sin @ ni dominio)
        const raw = String(j || '');
        const disp = raw.split('@')[0].split(':')[0].replace(/^@+/, '').replace(/^\+/, '');
        if (disp) unresolvedAssigneeDisplays.push(disp);
        incOnboardingFailure('mentions', 'invalid');
        return null;
      }
      const resolved = IdentityService.resolveAliasOrNull(norm);
      if (resolved) return resolved;
      // detectar si la mención proviene de un JID @lid (no plausible aunque sea numérico)
      const dom = String(j || '').split('@')[1]?.toLowerCase() || '';
      const fromLid = dom.includes('lid');
      const p = plausibility(norm, { fromLid });
      if (p.ok) return norm;
      // conservar para copy JIT
      unresolvedAssigneeDisplays.push(norm);
      incOnboardingFailure('mentions', p.reason!);
      return null;
    }).filter((id): id is string => !!id)
  ));

  // 2) Tokens de texto que empiezan por '@' como posibles asignados
  const atTokenCandidates = tokens.slice(2)
    .filter(t => t.startsWith('@'))
    .map(t => t.replace(/^@+/, '').replace(/^\+/, '').replace(/[.,;:!?)\]}¿¡"'’”]+$/, ''));
  const normalizedFromAtTokens = Array.from(new Set(
    atTokenCandidates.map((v) => {
      // Token especial: '@yo' → autoasignación; no cuenta como fallo
      if (String(v).toLowerCase() === 'yo') {
        return null;
      }
      const norm = normalizeWhatsAppId(v);
      if (!norm) {
        // agregar a no resolubles para JIT (texto ya viene sin @/+)
        if (v) unresolvedAssigneeDisplays.push(v);
        incOnboardingFailure('tokens', 'invalid');
        return null;
      }
      const resolved = IdentityService.resolveAliasOrNull(norm);
      if (resolved) return resolved;
      const p = plausibility(norm, { fromLid: false });
      if (p.ok) return norm;
      // conservar para copy JIT (preferimos el token limpio v)
      unresolvedAssigneeDisplays.push(v);
      incOnboardingFailure('tokens', p.reason!);
      return null;
    }).filter((id): id is string => !!id)
  ));

  // 3) Unir y deduplicar
  const combinedAssigneeCandidates = Array.from(new Set([
    ...mentionsNormalizedFromContext,
    ...normalizedFromAtTokens
  ]));

  const { description, dueDate, selfAssign } = parseNueva((context.message || '').trim(), mentionsNormalizedFromContext);

  // Asegurar creador
  const createdBy = ensureUserExists(context.sender, deps.db);
  if (!createdBy) {
    throw new Error('No se pudo asegurar el usuario creador');
  }

  // Normalizar menciones y excluir duplicados y el número del bot
  const botNumber = process.env.CHATBOT_PHONE_NUMBER || '';
  const assigneesNormalized = Array.from(new Set(
    [
      ...(selfAssign ? [context.sender] : []),
      ...combinedAssigneeCandidates
    ].filter(id => !botNumber || id !== botNumber)
  ));

  // Asegurar usuarios asignados
  const ensuredAssignees = assigneesNormalized
    .map(id => ensureUserExists(id, deps.db))
    .filter((id): id is string => !!id);

  // Asignación por defecto según contexto:
  // - En grupos: si no hay menciones → sin dueño (ningún asignado)
  // - En DM: si no hay menciones → asignada al creador
  let assignmentUserIds: string[] = [];
  if (ensuredAssignees.length > 0) {
    assignmentUserIds = ensuredAssignees;
  } else {
    assignmentUserIds = (context.groupId && isGroupId(context.groupId)) ? [] : [createdBy];
  }

  // Definir group_id solo si el grupo está activo
  const groupIdToUse = (context.groupId && GroupSyncService.isGroupActive(context.groupId))
    ? context.groupId
    : null;

  // Crear tarea y asignaciones
  const taskId = TaskService.createTask(
    {
      description: description || '',
      due_date: dueDate ?? null,
      group_id: groupIdToUse,
      created_by: createdBy,
    },
    assignmentUserIds.map(uid => ({
      user_id: uid,
      assigned_by: createdBy,
    }))
  );

  // Registrar origen del comando para esta tarea (si aplica)
  try {
    if (groupIdToUse && isGroupId(groupIdToUse) && context.messageId) {
      const participant = typeof context.participant === 'string' ? context.participant : null;
      const fromMe = typeof context.fromMe === 'boolean' ? (context.fromMe ? 1 : 0) : null;
      try {
        deps.db.prepare(`
          INSERT OR IGNORE INTO task_origins (task_id, chat_id, message_id, participant, from_me)
          VALUES (?, ?, ?, ?, ?)
        `).run(taskId, groupIdToUse, context.messageId, participant, fromMe);
      } catch {
        deps.db.prepare(`
          INSERT OR IGNORE INTO task_origins (task_id, chat_id, message_id)
          VALUES (?, ?, ?)
        `).run(taskId, groupIdToUse, context.messageId);
      }
    }
  } catch { }

  // Recuperar la tarea creada para obtener display_code asignado
  const createdTask = TaskService.getTaskById(taskId);

  const mentionsForSending = assignmentUserIds.map(uid => `${uid}@s.whatsapp.net`);

  // Resolver nombres útiles
  const groupName = groupIdToUse ? GroupSyncService.activeGroupsCache.get(groupIdToUse) : null;

  const assignedDisplayNames = await Promise.all(
    assignmentUserIds.map(async uid => {
      const name = await ContactsService.getDisplayName(uid);
      return name || uid;
    })
  );

  const responses: Msg[] = [];

  // 1) Ack al creador con formato compacto
  const dueFmt = formatDDMM(dueDate);
  const ownerPart = assignmentUserIds.length === 0
    ? `${ICONS.unassigned} ${groupName ? ` (${groupName})` : ''}`
    : `${assignmentUserIds.length > 1 ? '👥' : '👤'} ${assignedDisplayNames.join(', ')}`;
  const ackLines = [
    `${ICONS.create} ${codeId(taskId, createdTask?.display_code)} ${description || '(sin descripción)'}`,
    dueFmt ? `${ICONS.date} ${dueFmt}` : null,
    ownerPart
  ].filter(Boolean);
  responses.push({
    recipient: createdBy,
    message: [ackLines.join('\n'), '', CTA_HELP].join('\n'),
    mentions: mentionsForSending.length > 0 ? mentionsForSending : undefined
  });

  // 2) DM a cada asignado (excluyendo al creador para evitar duplicados)
  for (const uid of assignmentUserIds) {
    if (uid === createdBy) continue;
    responses.push({
      recipient: uid,
      message: [
        `${ICONS.assignNotice} ${codeId(taskId, createdTask?.display_code)}`,
        `${description || '(sin descripción)'}`,
        formatDDMM(dueDate) ? `${ICONS.date} ${formatDDMM(dueDate)}` : null,
        groupName ? `Grupo: ${groupName}` : null,
        `- Completar: \`/t x ${createdTask?.display_code}\``,
        `- Soltar: \`/t soltar ${createdTask?.display_code}\``
      ].filter(Boolean).join('\n') + '\n\n' + CTA_HELP,
      mentions: [`${createdBy}@s.whatsapp.net`]
    });
  }

  // A4: DM JIT al asignador si quedaron menciones/tokens irrecuperables
  responses.push(...buildJitAssigneePrompt(createdBy, context.groupId, unresolvedAssigneeDisplays));

  // Fase 2: disparar paquete de onboarding (2 DMs) tras crear tarea en grupo
  try {
    const gid = groupIdToUse || (isGroupId(context.groupId) ? context.groupId : null);
    maybeEnqueueOnboardingBundle(deps.db, {
      gid,
      createdBy,
      assignmentUserIds,
      taskId,
      displayCode: createdTask?.display_code ?? null,
      description: description || ''
    });
  } catch {}

  return responses;
}
