/**
 * Notification Preference
 *
 * One document per user describing which categories reach them, and through
 * which channels. Absence of a document means "everything on" — the defaults
 * below — so nothing has to be back-filled for existing users and a failure to
 * read preferences degrades to delivering the notification rather than losing
 * it.
 *
 * Preferences gate DELIVERY, not the event. An emitter still resolves the same
 * recipients; the preference decides whether a document is written for a given
 * person and whether the email channel is used.
 *
 * Critical-priority notifications ignore every setting here — see
 * `resolveChannels()`. Someone muting "billing" is asking not to hear about
 * invoices, not asking to miss their account being suspended.
 *
 * The email channel is off by default for every category, so turning it on is
 * itself the opt-in and `resolveChannels()` honours it for everything except
 * `low` priority chatter.
 */

import mongoose, { Document, Schema } from 'mongoose';
import type { NotificationCategory, NotificationChannel } from './Notification';

/**
 * Every category a preference can be expressed for.
 *
 * 'approval' was retired — its events moved to 'content' (approval decisions)
 * and 'support' (feature requests). Stored preference documents may still carry
 * an `approval` key; it is simply ignored, so no migration was needed.
 */
export const PREFERENCE_CATEGORIES: NotificationCategory[] = [
  'ai',
  'billing',
  'content',
  'account',
  'system',
  'social',
  'support',
];

export interface ICategoryChannelPreference {
  /** The bell and the notifications page. */
  inApp: boolean;
  /** A copy by email. Only ever used for high/critical items — see the service. */
  email: boolean;
}

export type DigestFrequency = 'off' | 'daily' | 'weekly';

export interface INotificationPreference extends Document {
  userId: string;
  /** Per-category channel switches. Missing keys fall back to the defaults. */
  channelsByCategory: Record<string, ICategoryChannelPreference>;
  /** Silences everything non-critical in one switch. */
  muteAll: boolean;
  /** Reserved for the digest mailer; stored now so the UI is not rebuilt later. */
  digestFrequency: DigestFrequency;
  createdAt: Date;
  updatedAt: Date;
}

/**
 * In-app on for everything; email off by default.
 *
 * Email defaults to off deliberately: SMTP is configured per deployment and may
 * be absent, and opting every user into mail for high-priority events would be
 * a behaviour change nobody asked for. Users turn it on per category.
 */
export function defaultChannelPreferences(): Record<string, ICategoryChannelPreference> {
  const out: Record<string, ICategoryChannelPreference> = {};
  for (const category of PREFERENCE_CATEGORIES) {
    out[category] = { inApp: true, email: false };
  }
  return out;
}

const CategoryChannelSchema = new Schema<ICategoryChannelPreference>(
  {
    inApp: { type: Boolean, default: true },
    email: { type: Boolean, default: false },
  },
  { _id: false },
);

const NotificationPreferenceSchema = new Schema<INotificationPreference>(
  {
    // One document per user; the unique index is what makes the upsert in
    // PUT /notifications/preferences safe under concurrent saves.
    userId: { type: String, required: true, unique: true, index: true },
    channelsByCategory: {
      type: Map,
      of: CategoryChannelSchema,
      default: () => defaultChannelPreferences(),
    },
    muteAll: { type: Boolean, default: false },
    digestFrequency: {
      type: String,
      enum: ['off', 'daily', 'weekly'],
      default: 'off',
    },
  },
  { timestamps: true },
);

/**
 * Resolve the channels a notification should actually use for one recipient.
 *
 * `preference` may be null (no document yet) — that is the common case and
 * means "use the defaults".
 */
export function resolveChannels(
  preference: INotificationPreference | null | undefined,
  category: NotificationCategory,
  priority: string,
): NotificationChannel[] {
  // Critical events are not negotiable. A muted category must never be the
  // reason someone misses a suspended account or a failed payment.
  if (priority === 'critical') return ['in_app', 'email'];

  if (!preference) return ['in_app'];
  if (preference.muteAll) return [];

  // Mongoose Maps need .get(); a plain object (mock DB, .lean()) does not.
  const raw = preference.channelsByCategory as any;
  const pref: ICategoryChannelPreference | undefined =
    typeof raw?.get === 'function' ? raw.get(category) : raw?.[category];

  const inApp = pref?.inApp ?? true;
  const email = pref?.email ?? false;

  const channels: NotificationChannel[] = [];
  if (inApp) channels.push('in_app');

  // Email is off by default for every category, so switching it on IS the
  // request to be emailed about that category — it is honoured here rather
  // than second-guessed.
  //
  // This used to also require `high` or `critical`, which made the switch a
  // no-op for the events people most expect mail about: a payment receipt,
  // an invoice and a plan change are all `normal`, so someone who turned on
  // Billing & subscription email received nothing at all and reasonably read
  // the feature as broken.
  //
  // `low` is still filtered out. Those are the pure-chatter types — generation
  // queued/cancelled, backup completed, export ready, profile updated, comments
  // on a feature request — and mailing them is what would get the whole feature
  // muted.
  if (email && priority !== 'low') channels.push('email');
  return channels;
}

export const NotificationPreference =
  mongoose.models.NotificationPreference ||
  mongoose.model<INotificationPreference>('NotificationPreference', NotificationPreferenceSchema);
