/**
 * Centralized email enqueue helpers used by AI agents AND manual API routes.
 * Keeps booking/order flows in sync regardless of how the record was created.
 */
import { enqueueEmail } from './outbox.service';
import { formatOrderItemsHtml, formatOrderSummaryHtml, formatBookingSummaryHtml, formatMoney, formatOrderTotalRows } from './format';
import { resolveRestaurantOwnerEmail, resolveRestaurantOwnerUser, getRestaurantNotificationMode, resolveBranchManagers } from './recipients';
import { escapeHtml } from './render';

import { childLogger } from '@server/logger';
const log = childLogger('svc.email.notify');

export interface OrderNotifyInput {
  restaurantId: string;
  branchId?: string | null;
  customerEmail?: string | null;
  customerName: string;
  orderRef: string;
  items: { name: string; quantity: number; price: number }[];
  subtotal?: number;
  tax?: number;
  total: number;
  deliveryType: string;
  couponCode?: string | null;
  discountAmount?: number | null;
  loyaltyPointsRedeemed?: number | null;
  loyaltyDiscount?: number | null;
  giftCardApplied?: number | null;
  giftCardRemainingBalance?: number | null;
}

export async function notifyOrderCreated(o: OrderNotifyInput): Promise<void> {
  if (o.customerEmail) {
    try {
      // Fall back to a items-only subtotal if caller didn't supply one, so the
      // breakdown is still meaningful for older code paths.
      const subtotalFallback = o.items.reduce((s, i) => s + (Number(i.price) || 0) * (Number(i.quantity) || 0), 0);
      const rows = formatOrderTotalRows({
        subtotal: o.subtotal ?? subtotalFallback,
        couponCode: o.couponCode,
        discountAmount: o.discountAmount ?? 0,
        loyaltyPointsRedeemed: o.loyaltyPointsRedeemed ?? 0,
        loyaltyDiscount: o.loyaltyDiscount ?? 0,
        giftCardApplied: o.giftCardApplied ?? 0,
        tax: o.tax ?? 0,
      });
      await enqueueEmail({
        to: o.customerEmail,
        templateKey: 'order_new_customer',
        channel: 'immediate',
        kind: 'order',
        restaurantId: o.restaurantId,
        branchId: o.branchId ?? undefined,
        vars: {
          customerName: o.customerName,
          orderRef: o.orderRef,
          itemsHtml: formatOrderItemsHtml(o.items),
          ...rows,
          couponHtml: o.couponCode && o.discountAmount && o.discountAmount > 0
            ? `<div style="margin-top:10px;padding:10px 12px;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:8px;font-size:13px;color:#047857;">Coupon <strong>${o.couponCode}</strong> applied — you saved <strong>${formatMoney(o.discountAmount)}</strong></div>`
            : '',
          giftCardHtml: o.giftCardApplied && o.giftCardApplied > 0
            ? `<div style="margin-top:10px;padding:10px 12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;font-size:13px;color:#1d4ed8;">Gift card applied — <strong>−${formatMoney(o.giftCardApplied)}</strong>${typeof o.giftCardRemainingBalance === 'number' ? ` · remaining balance <strong>${formatMoney(o.giftCardRemainingBalance)}</strong>` : ''}</div>`
            : '',
          total: formatMoney(o.total),
          deliveryType: o.deliveryType,
        },
      });
    } catch (e) { log.error({ err: e }, 'notify: order customer email failed'); }
  }
  try {
    const ownerEmail = await resolveRestaurantOwnerEmail(o.restaurantId);
    if (ownerEmail) {
      const mode = await getRestaurantNotificationMode(o.restaurantId);
      await enqueueEmail({
        to: ownerEmail,
        templateKey: 'order_new_restaurant',
        channel: mode,
        kind: 'order',
        restaurantId: o.restaurantId,
        vars: {
          count: 1,
          ordersHtml: formatOrderSummaryHtml({ ref: o.orderRef, customer: o.customerName, total: o.total, itemsCount: o.items.length }),
        },
      });
    }
  } catch (e) { log.error({ err: e }, 'notify: order restaurant email failed'); }
}

export interface BookingNotifyInput {
  restaurantId: string;
  branchId?: string | null;
  guestEmail?: string | null;
  guestName: string;
  bookingRef: string;
  date: string;
  time: string;
  partySize: number;
}

export async function notifyBookingCreated(b: BookingNotifyInput): Promise<void> {
  if (b.guestEmail) {
    try {
      await enqueueEmail({
        to: b.guestEmail,
        templateKey: 'booking_confirmation_customer',
        channel: 'immediate',
        kind: 'booking',
        restaurantId: b.restaurantId,
        branchId: b.branchId ?? undefined,
        vars: { guestName: b.guestName, bookingRef: b.bookingRef, date: b.date, time: b.time, partySize: b.partySize },
      });
      const dt = new Date(`${b.date}T${b.time}:00`);
      const reminderAt = new Date(dt.getTime() - 2 * 60 * 60 * 1000);
      if (reminderAt.getTime() > Date.now() + 60_000) {
        await enqueueEmail({
          to: b.guestEmail,
          templateKey: 'booking_reminder_customer',
          channel: 'immediate',
          kind: 'booking',
          restaurantId: b.restaurantId,
          branchId: b.branchId ?? undefined,
          scheduledFor: reminderAt,
          vars: { guestName: b.guestName, bookingRef: b.bookingRef, date: b.date, time: b.time, partySize: b.partySize },
        });
      }
    } catch (e) { log.error({ err: e }, 'notify: booking customer email failed'); }
  }
  try {
    const ownerEmail = await resolveRestaurantOwnerEmail(b.restaurantId);
    if (ownerEmail) {
      const mode = await getRestaurantNotificationMode(b.restaurantId);
      await enqueueEmail({
        to: ownerEmail,
        templateKey: 'booking_new_restaurant',
        channel: mode,
        kind: 'booking',
        restaurantId: b.restaurantId,
        vars: {
          count: 1,
          bookingsHtml: formatBookingSummaryHtml({ ref: b.bookingRef, guest: b.guestName, date: b.date, time: b.time, partySize: b.partySize }),
        },
      });
    }
  } catch (e) { log.error({ err: e }, 'notify: booking restaurant email failed'); }
}

// ───────────────────────── loyalty ─────────────────────────

export interface LoyaltyPointsEarnedInput {
  restaurantId: string;
  customerEmail: string;
  customerName: string;
  pointsEarned: number;
  newBalance: number;
  orderRef: string;
}

export async function notifyLoyaltyPointsEarned(i: LoyaltyPointsEarnedInput): Promise<void> {
  try {
    await enqueueEmail({
      to: i.customerEmail,
      templateKey: 'loyalty_points_earned',
      channel: 'immediate',
      kind: 'loyalty',
      restaurantId: i.restaurantId,
      vars: {
        customerName: i.customerName,
        pointsEarned: i.pointsEarned,
        newBalance: i.newBalance,
        orderRef: i.orderRef,
      },
    });
  } catch (e) { log.error({ err: e }, 'notify: loyalty points-earned email failed'); }
}

export interface LoyaltyTierUpInput {
  restaurantId: string;
  customerEmail: string;
  customerName: string;
  tierName: string;
  perksText: string | null;
  freeDelivery: boolean;
  autoDiscountPct: number;
  earnMultiplier: number;
}

function buildTierPerksHtml(i: LoyaltyTierUpInput): string {
  const items: string[] = [];
  if (i.perksText && i.perksText.trim()) items.push(escapeHtml(i.perksText.trim()));
  if (i.earnMultiplier && i.earnMultiplier !== 1) items.push(`${i.earnMultiplier}× points on every order`);
  if (i.autoDiscountPct && i.autoDiscountPct > 0) items.push(`${i.autoDiscountPct}% automatic discount on future orders`);
  if (i.freeDelivery) items.push(`Free delivery on every order`);
  if (items.length === 0) return '';
  const lis = items.map((p) => `<li style="padding:6px 0;color:#374151;font-size:14px;">${p}</li>`).join('');
  return `<div style="margin:14px 0;padding:14px 18px;background:#f9fafb;border-radius:10px;">
    <div style="font-size:13px;color:#6b7280;text-transform:uppercase;letter-spacing:1px;font-weight:600;margin-bottom:6px;">Your perks</div>
    <ul style="margin:0;padding-left:18px;list-style:disc;">${lis}</ul>
  </div>`;
}

// ───────────────────────── inventory ─────────────────────────

export interface LowStockAlert {
  restaurantId: string;
  branchId?: string | null;
  itemId: string;
  itemName: string;
  stockRemaining: number;
  threshold: number;
  /** Task #298: 'sold_out' uses the dedicated template; 'low_stock' is the
   *  threshold-crossing alert. Defaults to 'low_stock' for back-compat. */
  kind?: 'low_stock' | 'sold_out';
}

/**
 * Task #295/#298: notify the restaurant owner AND branch managers when an
 * item crosses its low-stock threshold (kind='low_stock') or sells out
 * (kind='sold_out'). Each kind has its own dedicated email template and
 * a separate 24h cooldown gate at the DB layer. Failures are swallowed
 * — alerts must never break the order flow that produced the decrement.
 *
 * In-app notifications are created once at the restaurant scope (so the
 * shared inbox shows them) plus a per-user copy for every branch manager
 * with a linked user account, so each manager sees the alert in their
 * personal feed.
 */
export async function notifyLowStock(a: LowStockAlert): Promise<void> {
  const kind: 'low_stock' | 'sold_out' = a.kind ?? 'low_stock';
  const isSoldOut = kind === 'sold_out';
  const templateKey = isSoldOut ? 'sold_out_alert' : 'low_stock_alert';
  const inAppType = isSoldOut ? 'inventory_sold_out' : 'inventory_low_stock';
  const inAppTitle = isSoldOut
    ? `Sold out: ${a.itemName}`
    : `Low stock: ${a.itemName}`;
  const inAppMessage = isSoldOut
    ? `${a.itemName} is now sold out. Restock or 86 the item.`
    : `${a.itemName} has ${a.stockRemaining} left (alert at ${a.threshold}).`;

  // Recipient set: owner + branch managers (deduped by lower-cased email).
  // We always include the owner because they're the catch-all when no
  // manager is assigned to the branch yet.
  const emails = new Set<string>();
  try {
    const ownerEmail = await resolveRestaurantOwnerEmail(a.restaurantId);
    if (ownerEmail) emails.add(ownerEmail.toLowerCase());
  } catch (e) { log.error({ err: e }, 'notify: resolve owner email failed'); }
  const managers = await resolveBranchManagers(a.restaurantId, a.branchId ?? null);
  for (const m of managers) {
    if (m.email) emails.add(m.email.toLowerCase());
  }

  try {
    const mode = await getRestaurantNotificationMode(a.restaurantId);
    for (const email of emails) {
      try {
        await enqueueEmail({
          to: email,
          templateKey,
          channel: mode,
          kind: 'inventory',
          restaurantId: a.restaurantId,
          branchId: a.branchId ?? undefined,
          vars: {
            itemName: a.itemName,
            stockRemaining: a.stockRemaining,
            threshold: a.threshold,
          },
        });
      } catch (e) { log.error({ err: e }, 'notify: inventory email enqueue failed'); }
    }
  } catch (e) { log.error({ err: e }, 'notify: inventory email batch failed'); }

  try {
    const { createNotification } = await import('@server/services/notifications.service');
    // Restaurant-wide notification (visible in shared inbox views).
    await createNotification(a.restaurantId, {
      type: inAppType,
      title: inAppTitle,
      message: inAppMessage,
      actionLabel: 'Manage inventory',
      actionHref: '/menu-management',
    });
    // Plus a per-user copy for each branch manager so it lands in their
    // personal inbox too. Duplicate of the shared row is intentional —
    // the shared one covers staff without a linked user account.
    const seenUsers = new Set<string>();
    for (const m of managers) {
      if (!m.userId || seenUsers.has(m.userId)) continue;
      seenUsers.add(m.userId);
      try {
        await createNotification(a.restaurantId, {
          type: inAppType,
          title: inAppTitle,
          message: inAppMessage,
          userId: m.userId,
          actionLabel: 'Manage inventory',
          actionHref: '/menu-management',
        });
      } catch (e) { log.error({ err: e }, 'notify: per-manager notification failed'); }
    }
  } catch (e) { log.error({ err: e }, 'notify: inventory in-app notification failed'); }
}

// ───────────────────────── webhooks ─────────────────────────

export interface WebhookExhaustedInput {
  restaurantId: string;
  eventType: string;
  endpointUrl: string;
}

/**
 * Task #505: notify the restaurant owner and restaurant-wide managers when a
 * webhook delivery attempt is exhausted (all retries used up). Creates per-user
 * in-app notifications (visible via the notifications API which filters by userId)
 * and sends an email to the owner. Failures are swallowed — alerts must never
 * break the retry worker that triggered them.
 */
export async function notifyWebhookExhausted(i: WebhookExhaustedInput): Promise<void> {
  const inAppTitle = `Webhook delivery exhausted: ${i.eventType}`;
  const inAppMessage = `All retry attempts for ${i.endpointUrl} failed. Visit the Webhooks dashboard to inspect logs and resend manually.`;
  const actionHref = '/webhooks';

  try {
    const { createNotification } = await import('@server/services/notifications.service');

    // Collect all recipients: restaurant owner + restaurant-wide staff owners/managers.
    // Per-user rows are required because the notifications API always filters by userId.
    const seenUsers = new Set<string>();

    const ownerUser = await resolveRestaurantOwnerUser(i.restaurantId).catch(() => null);
    if (ownerUser?.userId) {
      seenUsers.add(ownerUser.userId);
      await createNotification(i.restaurantId, {
        type: 'webhook_exhausted',
        title: inAppTitle,
        message: inAppMessage,
        userId: ownerUser.userId,
        actionLabel: 'View Webhooks',
        actionHref,
      }).catch((e) => log.error({ err: e }, 'notify: webhook exhausted owner notification failed'));
    }

    const managers = await resolveBranchManagers(i.restaurantId, null).catch(() => []);
    for (const m of managers) {
      if (!m.userId || seenUsers.has(m.userId)) continue;
      seenUsers.add(m.userId);
      await createNotification(i.restaurantId, {
        type: 'webhook_exhausted',
        title: inAppTitle,
        message: inAppMessage,
        userId: m.userId,
        actionLabel: 'View Webhooks',
        actionHref,
      }).catch((e) => log.error({ err: e }, 'notify: webhook exhausted manager notification failed'));
    }
  } catch (e) { log.error({ err: e }, 'notify: webhook exhausted in-app notification failed'); }

  try {
    const ownerEmail = await resolveRestaurantOwnerEmail(i.restaurantId);
    if (ownerEmail) {
      await enqueueEmail({
        to: ownerEmail,
        templateKey: 'webhook_delivery_exhausted',
        channel: 'immediate',
        kind: 'webhook',
        restaurantId: i.restaurantId,
        vars: {
          eventType: i.eventType,
          endpointUrl: i.endpointUrl,
        },
      });
    }
  } catch (e) { log.error({ err: e }, 'notify: webhook exhausted email failed'); }
}

export async function notifyLoyaltyTierUp(i: LoyaltyTierUpInput): Promise<void> {
  try {
    await enqueueEmail({
      to: i.customerEmail,
      templateKey: 'loyalty_tier_upgraded',
      channel: 'immediate',
      kind: 'loyalty',
      restaurantId: i.restaurantId,
      vars: {
        customerName: i.customerName,
        tierName: i.tierName,
        perksHtml: buildTierPerksHtml(i),
      },
    });
  } catch (e) { log.error({ err: e }, 'notify: loyalty tier-up email failed'); }
}
