/**
 * TrustedDevice Model
 *
 * Remembers a browser the user has explicitly marked as trusted, so that device
 * skips the second factor until the trust expires.
 *
 * Only the SHA-256 of the device token is stored. The raw token lives in the
 * browser and is never persisted server-side, so a database dump cannot be used
 * to bypass anyone's second factor. SHA-256 rather than bcrypt is correct here:
 * the token is 32 bytes of `crypto.randomBytes` (256 bits), not a low-entropy
 * user-chosen secret, and lookup must be a single indexed query on every login.
 *
 * Trust only ever waives the *second* factor — the password is still required.
 */

import mongoose, { Schema, Document, Types } from 'mongoose';

export interface ITrustedDevice extends Document {
  userId: Types.ObjectId;
  /** SHA-256 of the raw device token. Unique so a lookup is a single index hit. */
  tokenHash: string;
  /** Human label shown in the "your devices" list, e.g. "Chrome on Windows". */
  label: string;
  userAgent?: string;
  ipAddress?: string;
  lastUsedAt: Date;
  /** TTL-indexed: MongoDB removes the row itself once trust lapses. */
  expiresAt: Date;
  /** Set when revoked explicitly; kept briefly for the audit trail before TTL. */
  revokedAt?: Date | null;
  createdAt: Date;
  updatedAt: Date;
}

const TrustedDeviceSchema = new Schema<ITrustedDevice>({
  userId: {
    type: Schema.Types.ObjectId,
    ref: 'User',
    required: true,
    index: true,
  },
  tokenHash: {
    type: String,
    required: true,
    unique: true,
    index: true,
  },
  label: {
    type: String,
    trim: true,
    maxlength: [120, 'Device label cannot exceed 120 characters'],
    default: 'Unknown device',
  },
  userAgent: { type: String, trim: true, maxlength: 512 },
  ipAddress: { type: String, trim: true },
  lastUsedAt: { type: Date, default: Date.now },
  expiresAt: { type: Date, required: true },
  revokedAt: { type: Date, default: null },
}, {
  timestamps: true,
  toJSON: {
    // The hash is a bearer-token equivalent for lookup purposes — never expose it.
    transform: (_doc, ret) => {
      delete (ret as any).tokenHash;
      return ret;
    },
  },
});

TrustedDeviceSchema.index({ userId: 1, revokedAt: 1 });
// Expired trust is deleted by MongoDB rather than accumulating forever.
TrustedDeviceSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });

export const TrustedDevice =
  mongoose.models.TrustedDevice ||
  mongoose.model<ITrustedDevice>('TrustedDevice', TrustedDeviceSchema);
