/**
 * TwoFactorChallenge Model
 *
 * The server-side record behind every challenge token issued between a correct
 * password and a verified second factor.
 *
 * The token itself is a JWT and would be perfectly verifiable without this
 * document — but JWTs are stateless, and a challenge must be **single-use** and
 * **revocable**. Persisting the `jti` gives both: consuming a challenge marks it
 * here, so replaying the same token after a successful verification fails even
 * though its signature is still valid and it has not yet expired.
 *
 * `attempts` also gives brute-force counting that survives a process restart and
 * works across multiple instances, which an in-memory rate limiter cannot.
 */

import mongoose, { Schema, Document, Types } from 'mongoose';

export type ChallengePurpose = 'login' | 'enrollment';

export interface ITwoFactorChallenge extends Document {
  /** Matches the `jti` claim in the issued JWT. */
  jti: string;
  userId: Types.ObjectId;
  /** `login` = enrolled user owes a code. `enrollment` = mandatory mode, not yet set up. */
  purpose: ChallengePurpose;
  /** Set the moment the challenge is spent — a second use is refused. */
  consumedAt?: Date | null;
  /** Failed codes submitted against this specific challenge. */
  attempts: number;

  // ── Email OTP ──────────────────────────────────────────────────────────────
  // Held on the challenge rather than in a collection of its own so the code
  // inherits everything this document already guarantees: single-use
  // consumption, the TTL index, and the attempt counter. A delivered code
  // therefore cannot outlive the login attempt that produced it.

  /** SHA-256 of the delivered code, salted with this challenge's `jti`. */
  otpHash?: string;
  /** Independent of — and shorter than — the challenge's own expiry. */
  otpExpiresAt?: Date;
  /** Drives the resend cooldown. */
  otpSentAt?: Date;
  /** Resends used, capped by the policy. */
  otpResendCount: number;
  /** Masked address the code went to, for the audit trail and the UI hint. */
  otpDeliveredTo?: string;

  ipAddress?: string;
  userAgent?: string;
  /** TTL-indexed: expired challenges are removed by MongoDB. */
  expiresAt: Date;
  createdAt: Date;
  updatedAt: Date;
}

const TwoFactorChallengeSchema = new Schema<ITwoFactorChallenge>({
  jti: {
    type: String,
    required: true,
    unique: true,
    index: true,
  },
  userId: {
    type: Schema.Types.ObjectId,
    ref: 'User',
    required: true,
    index: true,
  },
  purpose: {
    type: String,
    enum: ['login', 'enrollment'],
    default: 'login',
  },
  consumedAt: { type: Date, default: null },
  attempts: { type: Number, default: 0 },
  // Email OTP — never stores the code itself, only its salted hash.
  otpHash: { type: String, select: false },
  otpExpiresAt: { type: Date },
  otpSentAt: { type: Date },
  otpResendCount: { type: Number, default: 0 },
  otpDeliveredTo: { type: String, trim: true },
  ipAddress: { type: String, trim: true },
  userAgent: { type: String, trim: true, maxlength: 512 },
  expiresAt: { type: Date, required: true },
}, {
  timestamps: true,
});

TwoFactorChallengeSchema.index({ jti: 1 }, { unique: true });
TwoFactorChallengeSchema.index({ userId: 1, createdAt: -1 });
TwoFactorChallengeSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });

export const TwoFactorChallenge =
  mongoose.models.TwoFactorChallenge ||
  mongoose.model<ITwoFactorChallenge>('TwoFactorChallenge', TwoFactorChallengeSchema);
