import type { Database } from 'bun:sqlite';
import { ResponseQueue } from './response-queue';
import { AllowedGroups } from './allowed-groups';
import { Metrics } from './metrics';
import { randomTokenBase64Url } from '../utils/crypto';
import { ICONS } from '../utils/icons';
import { codeId, code, bold, padTaskId } from '../utils/formatting';

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

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const isTest = () => process.env.NODE_ENV === 'test';
const isEnvTrue = (key: string) => ['true','1','yes','on'].includes(String(process.env[key] || '').toLowerCase());
const envNum = (key: string, fallback: number): number => {
  const v = Number(process.env[key]);
  return Number.isFinite(v) && v > 0 ? v : fallback;
};

function skipMetric(reason: string, gid: string): void {
  try { Metrics.inc('onboarding_dm_skipped_total', 1, { reason, group_id: String(gid || '') }); } catch {}
}

function sentMetric(source: string, gid: string, extra?: Record<string, any>): void {
  try { Metrics.inc('onboarding_bundle_sent_total', 1, { ...extra, group_id: String(gid) }); } catch {}
}

function promoSkipMetric(reason: string, groupId: string): void {
  try { Metrics.inc('onboarding_prompts_skipped_total', 1, { group_id: groupId, reason }); } catch {}
}

// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------

function isPromptsEnabled(): boolean {
  if (isTest()) return String(process.env.ONBOARDING_ENABLE_IN_TEST || '').toLowerCase() === 'true';
  const v = process.env.ONBOARDING_PROMPTS_ENABLED;
  return v == null ? true : ['true','1','yes'].includes(String(v).toLowerCase());
}

function checkCoverageBelowThreshold(ratio: number, groupId: string): boolean {
  const thrRaw = Number(process.env.ONBOARDING_COVERAGE_THRESHOLD);
  const threshold = Number.isFinite(thrRaw) ? Math.min(1, Math.max(0, thrRaw)) : 1;
  if (!(ratio < threshold)) {
    promoSkipMetric('coverage_100', groupId);
    return false;
  }
  return true;
}

function checkCoverageGracePeriod(db: Database, groupId: string, nowMs: number): boolean {
  const graceRaw = Number(process.env.ONBOARDING_GRACE_SECONDS);
  const graceSec = Number.isFinite(graceRaw) && graceRaw >= 0 ? Math.floor(graceRaw) : 90;

  const row = db.prepare('SELECT last_verified FROM groups WHERE id = ?').get(groupId) as any;
  const lv = row?.last_verified ? String(row.last_verified) : null;
  if (!lv) return true;

  const ms = parseIsoMs(lv);
  if (!Number.isFinite(ms)) return true;

  if (Math.floor((nowMs - ms) / 1000) < graceSec) {
    promoSkipMetric('grace_period', groupId);
    return false;
  }
  return true;
}

function checkCoverageCooldown(db: Database, groupId: string, nowMs: number): boolean {
  const cdRaw = Number(process.env.ONBOARDING_COOLDOWN_DAYS);
  const cdDays = Number.isFinite(cdRaw) && cdRaw >= 0 ? Math.floor(cdRaw) : 7;

  const row = db.prepare('SELECT onboarding_prompted_at FROM groups WHERE id = ?').get(groupId) as any;
  const promptedAt = row?.onboarding_prompted_at ? String(row.onboarding_prompted_at) : null;
  if (!promptedAt) return true;

  const ms = parseIsoMs(promptedAt);
  if (!Number.isFinite(ms)) return true;

  if ((nowMs - ms) < cdDays * 24 * 60 * 60 * 1000) {
    promoSkipMetric('cooldown_active', groupId);
    return false;
  }
  return true;
}

function validateBotNumber(groupId: string): string | null {
  const bot = String(process.env.CHATBOT_PHONE_NUMBER || '').trim();
  if (!bot || !/^\d+$/.test(bot)) {
    promoSkipMetric('missing_bot_number', groupId);
    return null;
  }
  return bot;
}

function enqueueCoveragePrompt(db: Database, groupId: string, bot: string): void {
  const msg = `Para poder asignarte tareas y acceder a la web, envía 'activar' al bot por privado. Solo hace falta una vez. Enlace: https://wa.me/${bot}`;
  db.transaction(() => {
    db.prepare(`
      INSERT INTO response_queue (recipient, message, status, attempts, metadata, created_at, updated_at, next_attempt_at)
      VALUES (?, ?, 'queued', 0, NULL, strftime('%Y-%m-%d %H:%M:%f','now'), strftime('%Y-%m-%d %H:%M:%f','now'), strftime('%Y-%m-%d %H:%M:%f','now'))
    `).run(groupId, msg);
    db.prepare(`UPDATE groups SET onboarding_prompted_at = strftime('%Y-%m-%d %H:%M:%f','now') WHERE id = ?`).run(groupId);
  })();
  try { Metrics.inc('onboarding_prompts_sent_total', 1, { group_id: groupId, reason: 'coverage_below_threshold' }); } catch {}
}

// ---------------------------------------------------------------------------
// JIT assignee prompt (unchanged except for minor cleanup)
// ---------------------------------------------------------------------------

export function buildJitAssigneePrompt(createdBy: string, groupId: string, unresolvedAssigneeDisplays: string[]): CommandResponse[] {
  const responses: CommandResponse[] = [];
  const unresolvedList = Array.from(new Set((unresolvedAssigneeDisplays || []).filter(Boolean)));
  if (unresolvedList.length === 0) return responses;

  const groupLabel = String(groupId && groupId.includes('@g.us') ? groupId : 'dm');
  if (!isPromptsEnabled()) { promoSkipMetric('disabled', groupLabel); return responses; }

  const bot = String(process.env.CHATBOT_PHONE_NUMBER || '').trim();
  if (!bot || !/^\d+$/.test(bot)) { promoSkipMetric('missing_bot_number', groupLabel); return responses; }

  const list = unresolvedList.join(', ');
  const groupCtx = (groupId && groupId.includes('@g.us')) ? ` (en el grupo ${groupId})` : '';
  const msg = `No puedo asignar a ${list} aún${groupCtx}. Pídele que toque este enlace y diga 'activar': https://wa.me/${bot}`;
  responses.push({ recipient: createdBy, message: msg });
  try { Metrics.inc('onboarding_prompts_sent_total', 1, { group_id: groupLabel, source: 'jit_assignee_failure' }); } catch { }

  return responses;
}

// ---------------------------------------------------------------------------
// Onboarding bundle — extracted pieces
// ---------------------------------------------------------------------------

interface OnboardingBundleParams {
  gid: string | null;
  createdBy: string;
  assignmentUserIds: string[];
  taskId: number;
  displayCode: number | null;
  description: string;
}

/** Returns false (and records a skip metric) when onboarding should not proceed. */
function checkOnboardingEnabled(params: OnboardingBundleParams): boolean {
  const baseEnabled = isEnvTrue('ONBOARDING_DM_ENABLED');
  const enabled = baseEnabled && (!isTest() || isEnvTrue('ONBOARDING_ENABLE_IN_TEST'));
  if (!enabled) { skipMetric('disabled', String(params.gid || '')); return false; }
  if (!params.gid) { skipMetric('no_group', ''); return false; }
  return true;
}

function checkGroupGating(gid: string): boolean {
  if (String(process.env.GROUP_GATING_MODE || 'off').toLowerCase() !== 'enforce') return true;
  try { return AllowedGroups.isAllowed(gid); } catch { return false; }
}

function checkDisplayCode(params: OnboardingBundleParams): boolean {
  if (typeof params.displayCode === 'number' && Number.isFinite(params.displayCode)) return true;
  skipMetric('no_display_code', String(params.gid || ''));
  return false;
}

/** Fetches active group members, excluding the creator, assignees, and the bot. */
function fetchEligibleMembers(db: Database, gid: string, params: OnboardingBundleParams): string[] {
  let members: string[] = [];
  try {
    const rows = db.prepare('SELECT user_id FROM group_members WHERE group_id = ? AND is_active = 1').all(gid) as Array<{ user_id: string }>;
    members = rows.map(r => String(r.user_id || '').trim()).filter(id => /^\d+$/.test(id) && id.length < 14);
  } catch {}

  const bot = String(process.env.CHATBOT_PHONE_NUMBER || '').trim();
  const exclude = new Set<string>([params.createdBy, ...params.assignmentUserIds]);
  return members.filter(id => !exclude.has(id) && (!bot || id !== bot));
}

function applyRecipientCap(recipients: string[], gid: string): string[] {
  const cap = Math.floor(envNum('ONBOARDING_EVENT_CAP', 30));
  if (recipients.length <= cap) return recipients;

  try { Metrics.inc('onboarding_recipients_capped_total', recipients.length - cap, { group_id: String(gid) }); } catch {}
  return recipients.slice(0, cap);
}

function resolveGroupLabel(db: Database, gid: string): string {
  try {
    const row = db.prepare('SELECT name FROM groups WHERE id = ?').get(gid) as any;
    const name = row?.name ? String(row.name).trim() : '';
    if (name) return name;
  } catch {}
  return gid;
}

function buildOnboardingMessage1(taskId: number, displayCode: string, description: string, groupLabel: string): string {
  const shortDesc = description.length > 100 ? (description.slice(0, 100) + '…') : description;
  return `¡Hola!, soy el bot de tareas. En ${bold(`'${groupLabel}'`)} acaban de crear una tarea: ${codeId(taskId, Number(displayCode))} _${shortDesc}_
- Para asignártela, escríbeme ${code(`t tomar ${padTaskId(Number(displayCode))}`)} por privado
- Más info: ${code('t info')} (por privado también)
${ICONS.info} Nunca escribo en grupos.
${ICONS.info} Cuando reciba tu primer mensaje no te enviaré más este recordatorio`;
}

function buildOnboardingMessage2(): string {
  return `*GUÍA RÁPIDA*
Puedes interactuar con el bot escribiéndome por privado:
- Ver todas las tareas: ${code('t todas')}
- Ver solo tus tareas: ${code('t mias')}
- ¿Quieres recordatorios?: ${code('t alarma diario|l-v|semanal|off')}
- Web: ${code('t web')}`;
}

// ---------------------------------------------------------------------------
// Cooldown & variant logic
// ---------------------------------------------------------------------------

type OnboardingVariant = 'initial' | 'reminder';

function cooldownDays(): number {
  const raw = Number(process.env.ONBOARDING_DM_COOLDOWN_DAYS);
  return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 14;
}

function bundleDelayMs(): number {
  const delayEnv = Number(process.env.ONBOARDING_BUNDLE_DELAY_MS);
  return Number.isFinite(delayEnv) && delayEnv >= 0 ? Math.floor(delayEnv) : 5000 + Math.floor(Math.random() * 5001);
}

function parseIsoMs(s: string): number {
  const iso = s.includes('T') ? s : (s.replace(' ', 'T') + 'Z');
  return Date.parse(iso);
}

function userHadInteractionSince(db: Database, rcpt: string, firstMs: number): boolean {
  try {
    const row = db.prepare('SELECT last_command_at FROM users WHERE id = ?').get(rcpt) as any;
    const lcRaw = row?.last_command_at ? String(row.last_command_at) : null;
    if (!lcRaw) return false;
    const lcMs = parseIsoMs(lcRaw);
    return Number.isFinite(lcMs) && lcMs > firstMs;
  } catch { return false; }
}

/**
 * Determines the onboarding variant for a recipient.
 * Returns null when the recipient should be skipped.
 */
function determineOnboardingVariant(
  db: Database,
  rcpt: string,
  cooldownDays: number,
  gid: string,
): OnboardingVariant | null {
  const stats = ResponseQueue.getOnboardingStats(rcpt);

  // Never received onboarding → initial
  if (!stats || (stats.total || 0) === 0) return 'initial';

  // No firstInitialAt → can't determine cooldown
  if (!stats.firstInitialAt) return null;

  const firstMs = parseIsoMs(String(stats.firstInitialAt));
  if (!Number.isFinite(firstMs)) return null;

  const cooldownMs = cooldownDays * 24 * 60 * 60 * 1000;
  const okCooldown = (Date.now() - firstMs) >= cooldownMs;
  const hadInteraction = userHadInteractionSince(db, rcpt, firstMs);

  if (okCooldown && !hadInteraction) return 'reminder';

  skipMetric(hadInteraction ? 'had_interaction' : 'cooldown_active', gid);
  return null;
}

// ---------------------------------------------------------------------------
// Bundle enqueue
// ---------------------------------------------------------------------------

function enqueueBundleForRecipient(
  rcpt: string,
  msg1: string,
  msg2: string,
  variant: OnboardingVariant,
  params: OnboardingBundleParams,
  delay2: number,
): void {
  const bundleId = randomTokenBase64Url(12);
  const gid = String(params.gid || '');
  const meta = { variant, group_id: gid, task_id: params.taskId, display_code: params.displayCode };

  ResponseQueue.enqueueOnboarding(rcpt, msg1, { ...meta, part: 1, bundle_id: bundleId }, 0);
  ResponseQueue.enqueueOnboarding(rcpt, msg2, { ...meta, part: 2, bundle_id: bundleId }, delay2);
  sentMetric(params.gid || '', gid, { variant });
}

// ---------------------------------------------------------------------------
// Main export
// ---------------------------------------------------------------------------

export function maybeEnqueueOnboardingBundle(db: Database, params: OnboardingBundleParams): void {
  // 1. Guard clauses
  if (!checkOnboardingEnabled(params)) return;
  const gid = params.gid!;
  if (!checkGroupGating(gid)) { skipMetric('not_allowed', gid); return; }
  if (!checkDisplayCode(params)) return;

  // 2. Fetch & filter recipients
  const members = fetchEligibleMembers(db, gid, params);
  if (members.length === 0) { skipMetric('no_members', gid); return; }

  const recipients = applyRecipientCap(members, gid);

  // 3. Build messages (once)
  const groupLabel = resolveGroupLabel(db, gid);
  const displayCodeStr = String(params.displayCode!);
  const desc = (params.description || '(sin descripción)').trim();
  const msg1 = buildOnboardingMessage1(params.taskId, displayCodeStr, desc, groupLabel);
  const msg2 = buildOnboardingMessage2();

  // 4. Cooldown & delay config
  const cd = cooldownDays();
  const delay2 = bundleDelayMs();

  // 5. Enqueue for each eligible recipient
  for (const rcpt of recipients) {
    const variant = determineOnboardingVariant(db, rcpt, cd, gid);
    if (!variant) continue;
    try {
      enqueueBundleForRecipient(rcpt, msg1, msg2, variant, params, delay2);
    } catch { /* best effort */ }
  }
}

// ---------------------------------------------------------------------------
// Group coverage prompt (unchanged)
// ---------------------------------------------------------------------------

export function publishGroupCoveragePrompt(db: Database, groupId: string, ratio: number): void {
  try {
    if (!isPromptsEnabled()) { promoSkipMetric('disabled', groupId); return; }
    if (!checkCoverageBelowThreshold(ratio, groupId)) return;
    if (!checkGroupGating(groupId)) { promoSkipMetric('not_allowed', groupId); return; }

    const nowMs = Date.now();
    if (!checkCoverageGracePeriod(db, groupId, nowMs)) return;
    if (!checkCoverageCooldown(db, groupId, nowMs)) return;

    const bot = validateBotNumber(groupId);
    if (!bot) return;

    enqueueCoveragePrompt(db, groupId, bot);
  } catch (e) {
    if (process.env.NODE_ENV !== 'test') console.warn('⚠️ Onboarding prompt skipped due to internal error for', groupId, e);
  }
}
