/**
 * AI Generation Notification Service
 *
 * Fire-and-forget helper that sends a single email notification whenever any AI
 * generation job completes successfully. Every AI-generating module funnels its
 * success through aiJobManager.completeJob(), so wiring the notification there
 * gives every module (Presentations, Content, Social Media, Newsletters, Ads, …)
 * the same behaviour without touching each route.
 *
 * Design rules (match featureRequestNotifications.ts):
 *  - Never throws — a failed email must never break AI generation.
 *  - Fully asynchronous — callers do not await it, so the UI / generation
 *    completion is never blocked or delayed.
 *  - Only fires on SUCCESS (completeJob). Failures (failJob) and cancellations
 *    never reach here, so no email is sent for them.
 *  - De-duplicated by jobId, so exactly one email is sent per completed
 *    generation even if completeJob is somehow invoked twice.
 *
 * The recipient address is read from the application configuration:
 *  - Super Admin → Settings → panelSettings.aiGenerationNotifications
 *    ({ enabled: boolean, recipients: string[] }), and/or
 *  - the AI_GENERATION_NOTIFY_EMAIL env var (comma-separated) as a fallback.
 */

import { getModels } from '../models';
import { sendSystemEmail } from './email/transactionalMailer';
import { PRODUCTION_APP_URL } from '../config/appUrls';
import { resolveChannels } from '../models/NotificationPreference';
import { getNotificationTypeDef } from './notificationTypes';

// ── Default config (applied when the field is missing from panelSettings) ────

export const DEFAULT_AI_GENERATION_NOTIFICATIONS = {
  enabled: false,
  recipients: [] as string[],
};

// ── De-dup guard: jobIds already notified (bounded to avoid unbounded growth) ─

const notifiedJobIds = new Set<string>();
const MAX_TRACKED_JOBS = 5000;

// ── Friendly module names for known job sources (moduleSource / moduleId) ────

const MODULE_LABELS: Record<string, string> = {
  'presentation-generator': 'Presentations',
  'newsletter': 'Newsletters',
  'social-media-os': 'Social Media',
  'ads': 'Ads',
  'blog': 'Blog Content',
  'blog-generation': 'Blog Content',
  'book': 'Books',
  'pr': 'PR',
  'email-templates': 'Email Templates',
  'whatsapp-nurturing': 'WhatsApp Nurturing',
  'landing-page': 'Landing Pages',
  'website-planner': 'Website Planner',
  'website-generator': 'Website Generator',
  'case-studies': 'Case Studies',
  'testimonials': 'Testimonials',
  'sales-script': 'Sales Scripts',
  'sales-collateral': 'Sales Collateral',
  'video-content': 'Video Content',
  'brand-strategy': 'Brand Strategy',
  'brand-assets': 'Brand Assets',
  'visual-identity': 'Visual Identity',
  // Without this the fallback cleaner renders "Hr Assets".
  'hr-assets': 'HR Assets',
  'icp': 'ICP',
  'persona': 'Personas',
  // AI Generate ICPs / Personas run as bulk jobs, so they reach the notifier
  // under these sources. Without them the generic fallback titles the email
  // "Icp Bulk" / "Persona Bulk".
  'icp-bulk': 'ICP',
  'persona-bulk': 'Personas',
  'competitor': 'Competitor Analysis',
  'moat-analysis': 'Moat Analysis',
  // Job sources are singular ('course', 'event') while the modules are plural,
  // and the generic fallback would title these "Course" / "Event" / "Gmb".
  'course': 'Courses',
  'event': 'Events',
  'gmb': 'Google Business Profile',
  'influencer': 'Influencer Marketing',
  'guerrilla-marketing': 'Guerrilla Marketing',
  'magazine-sponsorship': 'Magazine Sponsorship',
  'speaking-engagement': 'Speaking Engagements',
  'interview-media-prep': 'Interview & Media Prep',
  'loyalty-programme': 'Loyalty Programme',
  'membership-plan': 'Membership Plans',
  'referral': 'Referral Programme',
  'faq-bank': 'FAQ Bank',
  'sop': 'SOPs',
  'product': 'Products',
  'company-creation': 'Company Setup',
  // Without this the generic fallback titles the email "Executive Cv".
  'executive-cv': 'Executive CV',
  'marketing-channels-recommendations': 'Marketing Channels',
  // Without this the generic fallback titles the email "Seo".
  'seo': 'SEO',
  // Without this the generic fallback titles the email "Geo Optimization".
  'geo-optimization': 'AI Discoverability (GEO)',
};

/** Turn a job source/module id into a human-readable module name. */
function moduleLabel(source?: string, moduleId?: string): string {
  const key = (source || moduleId || '').toLowerCase();
  if (MODULE_LABELS[key]) return MODULE_LABELS[key];
  const cleaned = key
    .replace(/-(generator|generation)$/, '')
    .replace(/[-_]+/g, ' ')
    .trim();
  if (!cleaned) return 'AI Generation';
  return cleaned.replace(/\b\w/g, (c) => c.toUpperCase());
}

/** Best-effort extraction of a generated item's title/name from the job result. */
function extractTitle(data: Record<string, any> | undefined): string {
  if (!data || typeof data !== 'object') return '';
  const keys = ['title', 'name', 'campaignName', 'headline', 'subject', 'metaTitle', 'slug'];
  for (const k of keys) {
    const v = data[k];
    if (typeof v === 'string' && v.trim()) return v.trim();
  }
  // Common nested shapes (e.g. { campaign: { name } }, { generatedCampaign: { title } }).
  // 'autoFillData' covers modules that wrap the mapping rather than passing it
  // straight to completeJob — WhatsApp Nurturing sends { autoFillData, result }.
  for (const nest of ['campaign', 'generatedCampaign', 'presentation', 'data', 'autoFillData']) {
    const obj = data[nest];
    if (obj && typeof obj === 'object') {
      for (const k of keys) {
        const v = (obj as Record<string, any>)[k];
        if (typeof v === 'string' && v.trim()) return v.trim();
      }
    }
  }
  return '';
}

/**
 * Modules whose completion email also goes to the person who ran the generation.
 *
 * The notifier was built as an ops/admin alert: it emails a super-admin-configured
 * recipients list and nothing else, so the user who actually started a generation
 * never heard about it by email. These modules opt into also emailing that user.
 *
 * Deliberately an allow-list rather than a blanket change: `completeJob` is shared
 * by ~136 call sites across ~40 route files, and turning user-addressed mail on
 * everywhere would be an app-wide product change nobody asked for. Matched
 * against both `moduleSource` and `moduleId`, and by prefix, because the Brand
 * asset flows each register their own source ('brand-assets-watermark',
 * 'brand-assets-backdrop', …).
 */
const USER_EMAIL_MODULE_PREFIXES = [
  'brand-strategy',
  'visual-identity',
  'brand-assets',
  'brand-asset-guidelines',
  'stationery',
  'hr-assets',
  'ads',
  'email-templates',
  'intro-script',
  // SEO OS: bulk "Generate All" and single-record generation both notify through
  // /ai-context/seo/notify-generation, and the person who ran them expects to
  // hear that it finished — same as Email Templates above.
  'seo',
  // Case Studies ("Generate with AI" → /auto-fill, "Quick Generate" → /generate)
  // and Testimonials ("Generate with AI" → /auto-fill) already complete through
  // aiJobManager.completeJob, so they reach this notifier — they were simply not
  // on this list, which meant the only address they could ever resolve was the
  // super-admin ops list, and that ships turned off.
  'case-studies',
  'testimonials',
  // Same story for these four: every one of them already completes through
  // aiJobManager.completeJob and reaches this notifier, and every one of them
  // was missing from this list. 'website-generator' is the separate job the
  // Website Planner raises when it builds the site itself, so both halves of
  // that flow report.
  'faq-bank',
  'website-planner',
  'website-generator',
  'newsletter',
  'social-media-os',
  'course',
  'event',
  'interview-media-prep',
  'speaking-engagement',
  'guerrilla-marketing',
  'moat-analysis',
  'gmb',
  'wikipedia-profile',
  // Same cause once more, for these six. Each already raises its job through
  // aiJobManager.createJob/completeJob and so already reaches this notifier —
  // they were only ever missing from this list, which left the super-admin ops
  // list as the sole address they could resolve, and that ships disabled.
  // Keys are the exact strings passed to createJob() in each module's route:
  //   presentation-generator  routes/presentationGenerator.ts   (Presentations & Pitches)
  //   magazine-sponsorship    routes/aiContextMagazineSponsorship.ts
  //   sop                     routes/aiContextSop.ts
  //   referral                routes/aiContextReferral.ts
  //   membership-plan         routes/aiContextMembership.ts
  //   loyalty-programme       routes/aiContextLoyalty.ts
  'presentation-generator',
  'magazine-sponsorship',
  'sop',
  'referral',
  'membership-plan',
  'loyalty-programme',
];

function shouldEmailTriggeringUser(source?: string, moduleId?: string): boolean {
  const keys = [source, moduleId].filter(Boolean).map((k) => String(k).toLowerCase());
  return keys.some((key) => USER_EMAIL_MODULE_PREFIXES.some((p) => key === p || key.startsWith(`${p}-`)));
}

/**
 * Email address of the user who started the generation, or null.
 *
 * Best-effort and never throws: a job started outside a request has no userId,
 * and a deleted or malformed user must not stop the admin recipients from being
 * notified.
 */
async function getTriggeringUserEmail(userId?: string): Promise<string | null> {
  if (!userId) return null;
  try {
    const { User } = getModels();
    const user = await User.findById(userId).select('email').lean();
    const email = (user as any)?.email;
    return typeof email === 'string' && email.trim() ? email.trim() : null;
  } catch (err: any) {
    console.warn('[AiGenerationNotifications] Failed to resolve user email:', err?.message);
    return null;
  }
}

/**
 * Does this user want a completion email at all?
 *
 * Read from the same place the bell reads: their Notification Categories row for
 * AI generation. This used to be decided by a module allow-list here instead —
 * ~24 module prefixes got a user-addressed email and every other module got none,
 * which is why "AI generation email" worked in some modules and not others. The
 * module a job came from is not what should decide it; the person's own setting
 * is, and it is the same switch that governs the in-app entry beside it.
 *
 * Never throws: a preference lookup that fails falls back to the same defaults
 * `resolveChannels` applies to a user who has no document (email off).
 */
async function userWantsCompletionEmail(userId?: string): Promise<boolean> {
  if (!userId) return false;
  try {
    const { NotificationPreference } = getModels();
    const preference = NotificationPreference
      ? await NotificationPreference.findOne({ userId: String(userId) })
      : null;
    // Category and priority come from the registry rather than being repeated
    // here, so a change to how ai.generation.completed is classified moves this
    // with it.
    const def = getNotificationTypeDef('ai.generation.completed');
    return resolveChannels(preference, def.category, def.priority).includes('email');
  } catch (err: any) {
    console.warn('[AiGenerationNotifications] Failed to read notification preference:', err?.message);
    return false;
  }
}

/** Load the notification config from the super-admin panelSettings + env fallback. */
async function getNotificationConfig(): Promise<{ enabled: boolean; recipients: string[] }> {
  const envRecipients = (process.env.AI_GENERATION_NOTIFY_EMAIL || '')
    .split(',')
    .map((s) => s.trim())
    .filter(Boolean);

  try {
    const { User } = getModels();
    const superAdmin = await User.findOne({ role: 'super-admin' }).lean();
    const raw = (superAdmin as any)?.panelSettings?.aiGenerationNotifications;

    const configured = Array.isArray(raw?.recipients)
      ? raw.recipients.map((r: string) => String(r).trim()).filter(Boolean)
      : [];
    const recipients = Array.from(new Set([...configured, ...envRecipients]));

    // Enabled when explicitly turned on, or when only the env fallback is set.
    const enabled = raw?.enabled ?? (envRecipients.length > 0);
    return { enabled: !!enabled, recipients };
  } catch (err: any) {
    console.warn('[AiGenerationNotifications] Failed to load config:', err?.message);
    return { enabled: envRecipients.length > 0, recipients: envRecipients };
  }
}

/** Build the notification email HTML. */
function buildHtml(params: { module: string; title: string; timestamp: string }): string {
  const appUrl = (process.env.FRONTEND_URL || PRODUCTION_APP_URL).replace(/\/+$/, '');
  const titleRow = params.title
    ? `<tr><td style="padding:6px 0;color:#6b7280;">Item</td><td style="padding:6px 0;color:#111827;font-weight:600;">${escapeHtml(params.title)}</td></tr>`
    : '';
  return `<!DOCTYPE html>
<html><body style="margin:0;padding:24px;background:#f3f4f6;font-family:-apple-system,Segoe UI,Roboto,Arial,sans-serif;">
  <table role="presentation" width="100%" style="max-width:560px;margin:0 auto;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
    <tr><td style="background:#111827;padding:20px 24px;">
      <span style="color:#C8FF2E;font-size:13px;letter-spacing:.12em;text-transform:uppercase;">Mengo AI-CMO</span>
    </td></tr>
    <tr><td style="padding:24px;">
      <h1 style="margin:0 0 8px;font-size:18px;color:#111827;">✅ AI generation completed successfully</h1>
      <p style="margin:0 0 16px;color:#4b5563;font-size:14px;line-height:1.5;">
        Your AI generation has finished. Details below.
      </p>
      <table role="presentation" width="100%" style="font-size:14px;border-top:1px solid #e5e7eb;border-bottom:1px solid #e5e7eb;">
        <tr><td style="padding:6px 0;color:#6b7280;width:120px;">Module</td><td style="padding:6px 0;color:#111827;font-weight:600;">${escapeHtml(params.module)}</td></tr>
        ${titleRow}
        <tr><td style="padding:6px 0;color:#6b7280;">Completed</td><td style="padding:6px 0;color:#111827;">${escapeHtml(params.timestamp)}</td></tr>
      </table>
      <p style="margin:20px 0 0;">
        <a href="${appUrl}" style="display:inline-block;background:#C8FF2E;color:#0d1117;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;font-size:14px;">Open Mengo</a>
      </p>
    </td></tr>
    <tr><td style="padding:16px 24px;background:#f9fafb;color:#9ca3af;font-size:12px;">
      You received this because AI generation notifications are enabled in Super Admin settings.
    </td></tr>
  </table>
</body></html>`;
}

function escapeHtml(str: string): string {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

/**
 * Build the notification email (subject + HTML) for a completed AI generation.
 * Pure and side-effect free — exported so it can be unit-tested in isolation.
 */
export function buildAiGenerationEmail(params: {
  moduleSource?: string;
  moduleId?: string;
  autoFillData?: Record<string, any>;
  completedAt?: number;
}): { subject: string; html: string; moduleName: string; title: string; timestamp: string } {
  const moduleName = moduleLabel(params.moduleSource, params.moduleId);
  const title = extractTitle(params.autoFillData);
  const completedAt = new Date(params.completedAt || Date.now());
  const timestamp = completedAt.toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' });
  const subject = `AI Generation Completed${title ? `: ${title}` : ` — ${moduleName}`}`;
  const html = buildHtml({ module: moduleName, title, timestamp });
  return { subject, html, moduleName, title, timestamp };
}

// ── Diagnostics ──────────────────────────────────────────────────────────────

/**
 * Say once, clearly, why no completion email went out.
 *
 * The two failure modes are indistinguishable from "the code is broken" without
 * this: the feature ships disabled (DEFAULT_AI_GENERATION_NOTIFICATIONS) and an
 * enabled config with an empty recipient list also sends nothing.
 */
let warnedNotConfigured = false;
function warnNotConfiguredOnce(
  config: { enabled: boolean; recipients: string[] },
  userId?: string,
): void {
  if (warnedNotConfigured) return;
  warnedNotConfigured = true;

  // Two independent reasons land here, and saying which one saves a long hunt:
  // the job's user was never resolved or does not want the mail, and/or the ops
  // alert has no recipients.
  const userReason = userId
    ? `user ${userId} has Email switched off for AI generation in Settings → Notifications, or no address could be resolved for them`
    : 'the job had no attributed user (started outside a request, or the auth middleware did not capture one)';
  const adminReason = !config.enabled
    ? 'the ops alert is turned off'
    : 'the ops alert has no recipients configured';

  console.warn(
    '[AiGenerationNotifications] AI generation completed, but there was nobody to email: ' +
      `${userReason}; and ${adminReason}. ` +
      'For the ops copy, enable it under Super Admin → Settings → AI Generation Notifications ' +
      'and add a recipient, or set AI_GENERATION_NOTIFY_EMAIL (comma-separated). ' +
      'This warning is logged once per process.'
  );
}

// ── Main entry point ─────────────────────────────────────────────────────────

/**
 * Send a "AI generation completed" email. Fire-and-forget: this returns
 * immediately and performs the (async) send in the background. It never throws
 * and never blocks the caller, so AI generation always succeeds even if the
 * email fails.
 */
export function notifyAiGenerationCompleted(params: {
  jobId: string;
  moduleSource?: string;
  moduleId?: string;
  companyId?: string;
  /** Who started the generation. Emailed for the modules in the allow-list above. */
  userId?: string;
  autoFillData?: Record<string, any>;
  completedAt?: number;
}): void {
  const { jobId } = params;
  if (!jobId) return;

  // De-dup: never send twice for the same completed generation.
  if (notifiedJobIds.has(jobId)) return;

  void (async () => {
    try {
      const config = await getNotificationConfig();

      // Admin/ops recipients — unchanged, still gated on the super-admin config.
      const adminRecipients = config.enabled ? config.recipients : [];

      // The person who ran the generation. Independent of the admin config on
      // purpose: that toggle governs the ops alert, and requiring a super-admin
      // to enable it would mean a user never gets told their own generation
      // finished. Every module is treated the same now; the only gate is the
      // user's own AI generation → Email switch.
      const userEmail = (await userWantsCompletionEmail(params.userId))
        ? await getTriggeringUserEmail(params.userId)
        : null;

      // De-duplicated case-insensitively so a user who is also on the admin list
      // receives exactly one email.
      const seen = new Set<string>();
      const recipients: string[] = [];
      for (const to of [...(userEmail ? [userEmail] : []), ...adminRecipients]) {
        const key = to.toLowerCase();
        if (seen.has(key)) continue;
        seen.add(key);
        recipients.push(to);
      }

      // Nothing to send: no triggering user resolved AND no admin recipients.
      //
      // The admin config is deliberately NOT re-checked here. A previous
      // revision returned whenever `!config.enabled || !config.recipients.length`,
      // which threw away a perfectly good user-addressed email whenever the ops
      // alert was switched off — and since it ships disabled by default, that
      // meant no module ever emailed anyone. That is the "still no email after
      // completion" report. `recipients` above already encodes the real answer:
      // if it has an address, send to it.
      //
      // This used to return in total silence, which made the feature look broken
      // rather than unconfigured — the same "no email arrives" report came back
      // from five different modules before the cause was obvious. Logged once
      // per process so it is visible without flooding a bulk run.
      if (recipients.length === 0) {
        warnNotConfiguredOnce(config, params.userId);
        return;
      }

      // NOTE: there is deliberately no second gate on `config.enabled` here.
      // One used to sit at this point and returned whenever the super-admin ops
      // alert was off or had no recipients — which is the shipped default. It
      // ran AFTER `userEmail` had been resolved, so it threw that address away
      // too, and the "email the person who ran the generation" path above could
      // never fire for any module on USER_EMAIL_MODULE_PREFIXES. That is why a
      // completed generation (SEO bulk among them) still produced no mail on a
      // stock install. `adminRecipients` is already empty unless the ops alert
      // is enabled, so the toggle still governs exactly what it is meant to —
      // the admin/ops copy — and nothing else.

      // Marked only once we are actually going to send. Doing it before the
      // gate above burned the jobId: a generation that completed while the
      // feature was disabled (or while the config lookup failed) could never
      // notify afterwards, even though nothing had been sent.
      notifiedJobIds.add(jobId);
      if (notifiedJobIds.size > MAX_TRACKED_JOBS) {
        const oldest = notifiedJobIds.values().next().value;
        if (oldest) notifiedJobIds.delete(oldest);
      }

      const { subject, html } = buildAiGenerationEmail(params);

      for (const to of recipients) {
        try {
          const result = await sendSystemEmail({ to, subject, html });
          if (!result.success) {
            // `skipped` (SMTP not configured / sending disabled) used to pass in
            // silence, which is the other way this feature looks broken when it
            // is merely unconfigured: the notifier did its job and no mail was
            // ever handed to a transport. Both outcomes are logged now.
            console.warn(
              `[AiGenerationNotifications] ${result.skipped ? 'Skipped' : 'Send failed'} to ${to}: ${result.error}`
            );
          }
        } catch (sendErr: any) {
          // sendSystemEmail already swallows errors, but guard anyway.
          console.warn(`[AiGenerationNotifications] Send threw for ${to}:`, sendErr?.message);
        }
      }
    } catch (err: any) {
      console.error('[AiGenerationNotifications] notifyAiGenerationCompleted error:', err?.message);
    }
  })();
}
