/**
 * Token email notification orchestrator.
 *
 * Decides WHEN to send notification emails based on alert state and cooldown
 * windows. Called from the chat route after alert checks, and can also be
 * invoked manually or via cron for monthly summaries.
 *
 * All email sending is fire-and-forget — errors are logged but never
 * propagated to the caller. This ensures notifications never break the
 * main chat flow.
 */

import { logger } from "@/lib/logger";
import { sendDeviceOfflineEmail, sendBudgetAlertEmail, sendMonthlySummaryEmail } from "@/lib/email/send";
import { getTokenSpending, getSavingsEstimate } from "@/lib/token/analytics";
import { db } from "@/lib/db";
import type { TokenAlert } from "@/lib/token/alerts";

/* ── In-memory deduplication ────────────────────────────────────────── */

/**
 * Cooldown map prevents sending the same email type to the same user
 * within a defined window. Key format: `userId:emailType`.
 */
const emailCooldowns = new Map<string, number>();

const COOLDOWNS = {
  device_offline: 60 * 60 * 1000, // 1 hour between device offline emails
  budget_warning: 4 * 60 * 60 * 1000, // 4 hours between budget warning emails
  budget_critical: 1 * 60 * 60 * 1000, // 1 hour between critical budget emails
} as const;

/** Check whether a cooldown has expired. If so, update the timestamp and allow sending. */
function acquireCooldown(userId: string, emailType: keyof typeof COOLDOWNS): boolean {
  const key = `${userId}:${emailType}`;
  const now = Date.now();
  const lastSent = emailCooldowns.get(key);

  if (lastSent && now - lastSent < COOLDOWNS[emailType]) {
    return false; // still in cooldown
  }

  emailCooldowns.set(key, now);

  // Lazy prune: every 200 entries, remove expired cooldowns
  if (emailCooldowns.size > 200) {
    pruneCooldowns();
  }

  return true;
}

/** Remove expired cooldown entries. */
function pruneCooldowns(): void {
  const now = Date.now();
  const maxCooldown = Math.max(...Object.values(COOLDOWNS));
  for (const [key, timestamp] of emailCooldowns) {
    if (now - timestamp > maxCooldown) {
      emailCooldowns.delete(key);
    }
  }
}

/* ── Exported for testing only ───────────────────────────────────────── */

export function _resetCooldowns(): void {
  emailCooldowns.clear();
}

export function _getCooldownTimestamp(userId: string, emailType: string): number | undefined {
  return emailCooldowns.get(`${userId}:${emailType}`);
}

/* ── Alert notification processor ────────────────────────────────────── */

/**
 * Check alerts and send notification emails where appropriate.
 *
 * Called after `checkAlerts()` in the chat route. For each critical or
 * warning alert, checks cooldown and dispatches the right email.
 * All sends are wrapped in try/catch — failures are logged but never thrown.
 */
export async function processAlertNotifications(
  userId: string,
  userEmail: string,
  userName: string,
  alerts: TokenAlert[],
): Promise<void> {
  // Only process warning and critical alerts
  const actionable = alerts.filter(
    (a) => a.severity === "critical" || a.severity === "warning",
  );

  if (actionable.length === 0) {
    return;
  }

  for (const alert of actionable) {
    try {
      // Device offline alerts
      if (alert.category === "device" && alert.id.startsWith("device-offline-")) {
        if (!acquireCooldown(userId, "device_offline")) {
          logger.debug(
            { action: "token_notification_cooldown", alert_id: alert.id, user_id: userId },
            "Device offline email in cooldown",
          );
          continue;
        }

        // Extract lastSeen from the device to pass to the email template
        const device = alert.deviceId
          ? await db.device.findUnique({
              where: { id: alert.deviceId },
              select: { lastSeen: true, name: true },
            })
          : null;

        const deviceName = device?.name ?? alert.title.replace(" appears offline", "");
        const lastSeen = device?.lastSeen ?? alert.createdAt;

        await sendDeviceOfflineEmail(userEmail, userName, deviceName, lastSeen);

        logger.info(
          { action: "token_notification_sent", type: "device_offline", user_id: userId, device: deviceName },
          "Device offline notification email sent",
        );
      }

      // Budget critical alerts (>95%)
      if (alert.category === "budget" && alert.id === "budget-critical") {
        if (!acquireCooldown(userId, "budget_critical")) {
          logger.debug(
            { action: "token_notification_cooldown", alert_id: alert.id, user_id: userId },
            "Budget critical email in cooldown",
          );
          continue;
        }

        const config = await db.tokenConfig.findUnique({ where: { id: "singleton" } });
        const budgetLimit = config?.monthlyBudgetUsd ?? 50.0;
        const currentSpend = config?.totalSpentUsd ?? 0;
        const percentUsed = budgetLimit > 0 ? Math.round((currentSpend / budgetLimit) * 100) : 0;

        await sendBudgetAlertEmail(userEmail, userName, currentSpend, budgetLimit, percentUsed);

        logger.info(
          { action: "token_notification_sent", type: "budget_critical", user_id: userId, percent: percentUsed },
          "Budget critical notification email sent",
        );
      }

      // Budget warning alerts (>80%)
      if (alert.category === "budget" && alert.id === "budget-warning") {
        if (!acquireCooldown(userId, "budget_warning")) {
          logger.debug(
            { action: "token_notification_cooldown", alert_id: alert.id, user_id: userId },
            "Budget warning email in cooldown",
          );
          continue;
        }

        const config = await db.tokenConfig.findUnique({ where: { id: "singleton" } });
        const budgetLimit = config?.monthlyBudgetUsd ?? 50.0;
        const currentSpend = config?.totalSpentUsd ?? 0;
        const percentUsed = budgetLimit > 0 ? Math.round((currentSpend / budgetLimit) * 100) : 0;

        await sendBudgetAlertEmail(userEmail, userName, currentSpend, budgetLimit, percentUsed);

        logger.info(
          { action: "token_notification_sent", type: "budget_warning", user_id: userId, percent: percentUsed },
          "Budget warning notification email sent",
        );
      }
    } catch (err) {
      logger.error(
        { action: "token_notification_error", alert_id: alert.id, user_id: userId, err },
        "Failed to send notification email for alert",
      );
      // Swallow error — notifications must never fail the main request
    }
  }
}

/* ── Monthly summary ─────────────────────────────────────────────────── */

/**
 * Generate and send a monthly summary email for a user.
 *
 * Aggregates last month's Token spending, finds the top model,
 * computes savings, and sends the branded summary email.
 *
 * Can be called by a cron job, admin action, or manually triggered.
 */
export async function sendMonthlySummaryForUser(
  userId: string,
  userEmail: string,
  userName: string,
): Promise<void> {
  try {
    const [spending, savings] = await Promise.all([
      getTokenSpending(userId),
      getSavingsEstimate(userId),
    ]);

    // Determine the month name for the summary (previous month)
    const now = new Date();
    const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
    const month = lastMonth.toLocaleString("en-US", { month: "long", year: "numeric" });

    // Find the top model by cost
    let topModel = "None";
    let topModelCost = 0;
    for (const [model, cost] of Object.entries(spending.byModel)) {
      if (cost > topModelCost) {
        topModel = model;
        topModelCost = cost;
      }
    }

    // Skip sending if there were no messages (nothing to summarize)
    if (spending.messageCount === 0) {
      logger.debug(
        { action: "token_monthly_summary_skip", user_id: userId },
        "No messages to summarize — skipping monthly email",
      );
      return;
    }

    await sendMonthlySummaryEmail(userEmail, {
      name: userName,
      month,
      totalMessages: spending.messageCount,
      totalCost: spending.allTime,
      topModel,
      topModelCost,
      savingsUsd: savings.savingsUsd,
      savingsPct: savings.savingsPct,
    });

    logger.info(
      {
        action: "token_monthly_summary_sent",
        user_id: userId,
        month,
        messages: spending.messageCount,
        cost: spending.allTime,
      },
      "Monthly summary email sent",
    );
  } catch (err) {
    logger.error(
      { action: "token_monthly_summary_error", user_id: userId, err },
      "Failed to send monthly summary email",
    );
    // Swallow error — notifications must never fail the caller
  }
}
