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[];
};

// ---------------------------------------------------------------------------
// Config & helpers
// ---------------------------------------------------------------------------

type FailReason = 'non_numeric' | 'too_short' | 'too_long' | 'from_lid' | 'invalid';

function getFallbackDigitLimits(): { min: number; max: number } {
  const min = parseInt((process.env.ONBOARDING_FALLBACK_MIN_DIGITS || '8').trim(), 10);
  const max = parseInt((process.env.ONBOARDING_FALLBACK_MAX_DIGITS || '15').trim(), 10);
  return {
    min: Number.isFinite(min) && min > 0 ? min : 8,
    max: Number.isFinite(max) && max > 0 ? max : 15,
  };
}

function isDigits(s: string): boolean { return /^\d+$/.test(s); }

function checkPlausibility(s: string, limits: { min: number; max: number }, fromLid: boolean): { ok: boolean; reason?: FailReason } {
  if (!s) return { ok: false, reason: 'invalid' };
  if (fromLid) return { ok: false, reason: 'from_lid' };
  if (!isDigits(s)) return { ok: false, reason: 'non_numeric' };
  if (s.length < limits.min) return { ok: false, reason: 'too_short' };
  if (s.length >= limits.max) return { ok: false, reason: 'too_long' };
  return { ok: true };
}

function recordOnboardingFailure(groupId: string, source: 'mentions' | 'tokens', reason: FailReason): void {
  try {
    const gid = isGroupId(groupId) ? groupId : 'dm';
    Metrics.inc('onboarding_assign_failures_total', 1, { group_id: String(gid), source, reason });
  } catch {}
}

// ---------------------------------------------------------------------------
// Mention processing
// ---------------------------------------------------------------------------

interface MentionResult {
  ids: string[];
  unresolved: string[];
}

/** Extracts a display string from a raw JID (strips @domain, @, +). */
function displayFromJid(raw: string): string {
  return raw.split('@')[0].split(':')[0].replace(/^@+/, '').replace(/^\+/, '');
}

function processContextMentions(mentions: string[], limits: { min: number; max: number }, groupId: string): MentionResult {
  const ids: string[] = [];
  const unresolved: string[] = [];

  for (const j of new Set(mentions)) {
    const norm = normalizeWhatsAppId(j);
    if (!norm) {
      const disp = displayFromJid(j);
      if (disp) unresolved.push(disp);
      recordOnboardingFailure(groupId, 'mentions', 'invalid');
      continue;
    }

    const resolved = IdentityService.resolveAliasOrNull(norm);
    if (resolved) { ids.push(resolved); continue; }

    const dom = String(j).split('@')[1]?.toLowerCase() || '';
    const fromLid = dom.includes('lid');
    const p = checkPlausibility(norm, limits, fromLid);

    if (p.ok) { ids.push(norm); continue; }

    unresolved.push(norm);
    recordOnboardingFailure(groupId, 'mentions', p.reason!);
  }

  return { ids, unresolved };
}

function processAtTokens(tokens: string[], limits: { min: number; max: number }, groupId: string): MentionResult {
  const candidates = tokens
    .filter(t => t.startsWith('@'))
    .map(t => t.replace(/^@+/, '').replace(/^\+/, '').replace(/[.,;:!?)\]}¿¡"'’”]+$/, ''));

  const ids: string[] = [];
  const unresolved: string[] = [];

  for (const v of new Set(candidates)) {
    // '@yo' → self-assignment marker, not an actual user
    if (String(v).toLowerCase() === 'yo') continue;

    const norm = normalizeWhatsAppId(v);
    if (!norm) {
      if (v) unresolved.push(v);
      recordOnboardingFailure(groupId, 'tokens', 'invalid');
      continue;
    }

    const resolved = IdentityService.resolveAliasOrNull(norm);
    if (resolved) { ids.push(resolved); continue; }

    const p = checkPlausibility(norm, limits, false);
    if (p.ok) { ids.push(norm); continue; }

    unresolved.push(v);
    recordOnboardingFailure(groupId, 'tokens', p.reason!);
  }

  return { ids, unresolved };
}

// ---------------------------------------------------------------------------
// Assignment resolution
// ---------------------------------------------------------------------------

function resolveFinalAssignees(
  candidates: string[],
  selfAssign: boolean,
  sender: string,
  groupId: string,
  db: Database,
): { ensured: string[]; userIds: string[] } {
  const botNumber = process.env.CHATBOT_PHONE_NUMBER || '';
  const source = Array.from(new Set([
    ...(selfAssign ? [sender] : []),
    ...candidates,
  ].filter(id => !botNumber || id !== botNumber)));

  const ensured = source
    .map(id => ensureUserExists(id, db))
    .filter((id): id is string => !!id);

  // Default: in groups → no assignment; in DMs → assign to creator
  const userIds = ensured.length > 0
    ? ensured
    : (isGroupId(groupId) ? [] : [sender]);

  return { ensured, userIds };
}

// ---------------------------------------------------------------------------
// Response building
// ---------------------------------------------------------------------------

function buildAcknowledgement(
  taskId: number,
  displayCode: number | null,
  description: string,
  dueDate: string | null,
  assignmentUserIds: string[],
  assignedDisplayNames: string[],
  groupName: string | null,
  createdBy: string,
): Msg {
  const dueFmt = formatDDMM(dueDate);
  const ownerPart = assignmentUserIds.length === 0
    ? `${ICONS.unassigned} ${groupName ? ` (${groupName})` : ''}`
    : `${assignmentUserIds.length > 1 ? '👥' : '👤'} ${assignedDisplayNames.join(', ')}`;

  const lines = [
    `${ICONS.create} ${codeId(taskId, displayCode)} ${description || '(sin descripción)'}`,
    dueFmt ? `${ICONS.date} ${dueFmt}` : null,
    ownerPart,
  ].filter(Boolean);

  return {
    recipient: createdBy,
    message: [lines.join('\n'), '', CTA_HELP].join('\n'),
    ...(assignmentUserIds.length > 0 ? { mentions: assignmentUserIds.map(uid => `${uid}@s.whatsapp.net`) } : {}),
  };
}

function buildAssigneeNotices(
  taskId: number,
  displayCode: number | null,
  description: string,
  dueDate: string | null,
  assignmentUserIds: string[],
  createdBy: string,
  groupName: string | null,
): Msg[] {
  const notices: Msg[] = [];
  for (const uid of assignmentUserIds) {
    if (uid === createdBy) continue;
    notices.push({
      recipient: uid,
      message: [
        `${ICONS.assignNotice} ${codeId(taskId, displayCode)}`,
        description || '(sin descripción)',
        formatDDMM(dueDate) ? `${ICONS.date} ${formatDDMM(dueDate)}` : null,
        groupName ? `Grupo: ${groupName}` : null,
        `- Completar: \`t x ${displayCode}\``,
        `- Soltar: \`t soltar ${displayCode}\``,
      ].filter(Boolean).join('\n') + '\n\n' + CTA_HELP,
      mentions: [`${createdBy}@s.whatsapp.net`],
    });
  }
  return notices;
}

function recordTaskOrigin(db: Database, taskId: number, groupId: string, ctx: Ctx): void {
  if (!isGroupId(groupId) || !ctx.messageId) return;
  try {
    const participant = typeof ctx.participant === 'string' ? ctx.participant : null;
    const fromMe = typeof ctx.fromMe === 'boolean' ? (ctx.fromMe ? 1 : 0) : null;
    try {
      db.prepare(`
        INSERT OR IGNORE INTO task_origins (task_id, chat_id, message_id, participant, from_me)
        VALUES (?, ?, ?, ?, ?)
      `).run(taskId, groupId, ctx.messageId, participant, fromMe);
    } catch {
      db.prepare(`
        INSERT OR IGNORE INTO task_origins (task_id, chat_id, message_id)
        VALUES (?, ?, ?)
      `).run(taskId, groupId, ctx.messageId);
    }
  } catch {}
}

// ---------------------------------------------------------------------------
// Main handler
// ---------------------------------------------------------------------------

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

  // 1. Process mentions from context and @tokens from text
  const mentionResult = processContextMentions(context.mentions || [], limits, context.groupId);
  const tokenResult = processAtTokens(tokens.slice(2), limits, context.groupId);

  const combinedCandidates = Array.from(new Set([
    ...mentionResult.ids,
    ...tokenResult.ids,
  ]));
  const unresolvedDisplays = Array.from(new Set([
    ...mentionResult.unresolved,
    ...tokenResult.unresolved,
  ]));

  // 2. Parse command
  const { description, dueDate, selfAssign } = parseNueva(
    (context.message || '').trim(),
    mentionResult.ids,
  );

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

  // 4. Resolve assignees
  const { ensured: ensuredAssignees, userIds: assignmentUserIds } = resolveFinalAssignees(
    combinedCandidates, selfAssign, context.sender, context.groupId, deps.db,
  );

  // 5. Determine group
  const groupIdToUse = (context.groupId && GroupSyncService.isGroupActive(context.groupId))
    ? context.groupId : null;

  // 6. Create task
  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 })),
  );

  // 7. Record origin
  if (groupIdToUse) recordTaskOrigin(deps.db, taskId, groupIdToUse, context);

  // 8. Fetch created task & display names
  const createdTask = TaskService.getTaskById(taskId);
  const groupName = groupIdToUse ? (GroupSyncService.activeGroupsCache.get(groupIdToUse) ?? null) : null;
  const displayNames = await Promise.all(
    assignmentUserIds.map(uid => ContactsService.getDisplayName(uid).then(n => n || uid)),
  );

  // 9. Build responses
  const responses: Msg[] = [];
  responses.push(buildAcknowledgement(taskId, createdTask?.display_code ?? null, description || '', dueDate, assignmentUserIds, displayNames, groupName, createdBy));
  responses.push(...buildAssigneeNotices(taskId, createdTask?.display_code ?? null, description || '', dueDate, assignmentUserIds, createdBy, groupName));
  responses.push(...buildJitAssigneePrompt(createdBy, context.groupId, unresolvedDisplays));

  // 10. Onboarding bundle
  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;
}
