/**
 * Notification Manager
 *
 * Smart notification throttling and delivery logic.
 * Decides whether to send a push notification based on:
 * - Device token availability
 * - App background state (only send when backgrounded)
 * - Throttle limits (max 4 notifications per hour per player)
 * - Event importance (category-based priority)
 */

import { sendPushNotification, type PushPayload, type NotificationCategory, type SendResult, type DeepLinkType } from './pushNotificationService.js';
import type { Player } from '../../models/Player.js';

const MAX_NOTIFICATIONS_PER_HOUR = 4;

/** Per-player throttle tracking */
interface ThrottleEntry {
  timestamps: number[]; // Send timestamps within the current hour window
}

const throttleMap = new Map<string, ThrottleEntry>();

/** Clean old timestamps from a throttle entry (older than 1 hour) */
function cleanThrottleEntry(entry: ThrottleEntry): void {
  const oneHourAgo = Date.now() - 60 * 60 * 1000;
  entry.timestamps = entry.timestamps.filter((t) => t > oneHourAgo);
}

/** Check if a player is within the throttle limit.
 *  Milestone-category events (priority 3) are only throttled when all 4 slots are used.
 *  Other categories are throttled when 3 slots are used, reserving 1 for milestones. */
function isThrottled(userId: string, category?: NotificationCategory): boolean {
  const entry = throttleMap.get(userId);
  if (!entry) return false;

  cleanThrottleEntry(entry);

  const used = entry.timestamps.length;
  // Milestone events (priority 3) can use the last reserved slot
  if (category && CATEGORY_PRIORITY[category] >= 3) {
    return used >= MAX_NOTIFICATIONS_PER_HOUR;
  }
  // Other categories: throttle at 3 to reserve 1 slot for milestones
  return used >= MAX_NOTIFICATIONS_PER_HOUR - 1;
}

/** Record that a notification was sent for throttling purposes */
function recordSend(userId: string): void {
  let entry = throttleMap.get(userId);
  if (!entry) {
    entry = { timestamps: [] };
    throttleMap.set(userId, entry);
  }
  cleanThrottleEntry(entry);
  entry.timestamps.push(Date.now());
}

/** Get remaining notification budget for a player this hour */
export function getRemainingBudget(userId: string): number {
  const entry = throttleMap.get(userId);
  if (!entry) return MAX_NOTIFICATIONS_PER_HOUR;

  cleanThrottleEntry(entry);
  return Math.max(0, MAX_NOTIFICATIONS_PER_HOUR - entry.timestamps.length);
}

/**
 * Priority levels for notification categories.
 * Higher priority categories can bypass certain soft limits.
 */
const CATEGORY_PRIORITY: Record<NotificationCategory, number> = {
  milestone: 3,
  relationship: 2,
  life_event: 2,
  holiday: 1,
  reminder: 1,
};

export interface NotifyOptions {
  /** Player's user ID (for throttling) */
  userId: string;
  /** Device token for push delivery */
  deviceToken: string;
  /** Whether the app is currently in the background */
  isBackgrounded: boolean;
  /** Notification payload */
  payload: PushPayload;
}

export interface NotifyResult {
  sent: boolean;
  reason?: string;
  sendResult?: SendResult;
}

/**
 * Attempt to send a push notification with smart throttling.
 *
 * Checks:
 * 1. Device token is present
 * 2. App is backgrounded (no point sending push if user is active)
 * 3. Player hasn't exceeded hourly throttle limit
 * 4. Delegates to pushNotificationService for actual delivery
 */
export async function notify(options: NotifyOptions): Promise<NotifyResult> {
  const { userId, deviceToken, isBackgrounded, payload } = options;

  // Check device token
  if (!deviceToken) {
    return { sent: false, reason: 'no_device_token' };
  }

  // Only send when app is backgrounded
  if (!isBackgrounded) {
    return { sent: false, reason: 'app_in_foreground' };
  }

  // Check throttle (milestone events get a reserved slot)
  if (isThrottled(userId, payload.category)) {
    console.log(
      `[NotificationManager] Throttled for ${userId} (${MAX_NOTIFICATIONS_PER_HOUR}/hr limit reached)`
    );
    return { sent: false, reason: 'throttled' };
  }

  // Send the notification
  const sendResult = await sendPushNotification(deviceToken, payload);

  if (sendResult.success) {
    recordSend(userId);
    console.log(
      `[NotificationManager] Sent "${payload.category ?? 'general'}" notification to ${userId}`
    );
  } else {
    console.warn(
      `[NotificationManager] Failed to send to ${userId}: ${sendResult.error}`
    );
  }

  return { sent: sendResult.success, sendResult };
}

/**
 * Convenience: send a realtime event notification.
 * Called from PlayerSession when a realtime-mode event fires and the app is backgrounded.
 */
export async function notifyRealtimeEvent(
  userId: string,
  deviceToken: string,
  isBackgrounded: boolean,
  eventTitle: string,
  eventMessage: string,
  category: NotificationCategory = 'life_event',
  deepLinkId?: string
): Promise<NotifyResult> {
  const deepLinkTypeMap: Record<NotificationCategory, DeepLinkType> = {
    life_event: 'event',
    relationship: 'chat',
    milestone: 'milestone',
    holiday: 'event',
    reminder: 'event',
  };

  return notify({
    userId,
    deviceToken,
    isBackgrounded,
    payload: {
      title: eventTitle,
      body: eventMessage,
      category,
      sound: 'default',
      deepLink: deepLinkId
        ? { type: deepLinkTypeMap[category] ?? 'event', id: deepLinkId }
        : undefined,
    },
  });
}

/**
 * Check if a push notification should be sent for a given player.
 * Checks device token, connection state (as proxy for backgrounded), and throttle.
 */
export function shouldSendNotification(player: Player, category?: NotificationCategory): boolean {
  if (!player.deviceToken) return false;
  if (player.connection !== 'disconnected') return false;
  if (isThrottled(player.userId, category)) return false;
  return true;
}

/**
 * Queue and send a realtime notification for a player if appropriate.
 * High-level convenience for use in the game loop.
 */
export async function queueRealtimeNotification(
  player: Player,
  event: { title: string; body: string; type: NotificationCategory; id?: string }
): Promise<NotifyResult> {
  if (!shouldSendNotification(player, event.type)) {
    const reason = !player.deviceToken
      ? 'no_device_token'
      : player.connection !== 'disconnected'
        ? 'app_in_foreground'
        : 'throttled';
    return { sent: false, reason };
  }

  return notifyRealtimeEvent(
    player.userId,
    player.deviceToken,
    true,
    event.title,
    event.body,
    event.type,
    event.id
  );
}

/**
 * Fire a "relationship at risk" nudge (T010d). Used when an NPC the player once
 * felt strongly about (affinity was high) has decayed past the at-risk threshold
 * through neglect. Sent as a 'reminder'/relationship-category push so it is
 * testable without real APNs (stub mode / shouldSendNotification gating apply).
 */
export async function notifyRelationshipAtRisk(
  player: Player,
  npcName: string,
  deepLinkId?: string
): Promise<NotifyResult> {
  const title = 'Drifting apart';
  const body = `${npcName} feels distant. You haven't connected in a while — reach out before it's too late.`;
  if (!shouldSendNotification(player, 'reminder')) {
    const reason = !player.deviceToken
      ? 'no_device_token'
      : player.connection !== 'disconnected'
        ? 'app_in_foreground'
        : 'throttled';
    return { sent: false, reason };
  }
  return notifyRealtimeEvent(
    player.userId,
    player.deviceToken,
    true,
    title,
    body,
    'reminder',
    deepLinkId
  );
}

/**
 * Clear throttle data for a specific user.
 * Call when a player disconnects to free memory.
 */
export function clearThrottle(userId: string): void {
  throttleMap.delete(userId);
}

/**
 * Clear all throttle data. Useful for tests.
 */
export function clearAllThrottles(): void {
  throttleMap.clear();
}
