/**
 * User Model
 */

import mongoose, { Schema, Document } from 'mongoose';
import bcrypt from 'bcryptjs';

export type UserRole = 'super-admin' | 'admin' | 'manager' | 'editor' | 'viewer';

export type UserStatus = 'active' | 'inactive' | 'suspended';

export interface IUser extends Document {
  email: string;
  name: string;
  role: UserRole;
  username?: string;
  phone?: string;
  status: UserStatus;
  profileImage?: string;
  notes?: string;
  lastLoginAt?: Date;
  roleId?: mongoose.Types.ObjectId;
  companyIds: string[];
  activeCompanyId: string | null;
  avatar?: string;
  passwordHash: string;
  panelSettings?: any;
  userSettings?: {
    useGlobalAIConfig?: boolean;
    aiConfig?: any;
    preferredTextModel?: string;
    rememberTextModelChoice?: boolean;
    aiModelPreferences?: {
      text?: { model?: string; remember?: boolean };
      image?: { model?: string; remember?: boolean };
      video?: { model?: string; remember?: boolean };
      audio?: { model?: string; remember?: boolean };
    };
  };
  mustChangePassword?: boolean;
  /** SHA-256 hash of the outstanding password-reset token (raw token is only ever emailed). */
  passwordResetTokenHash?: string;
  /** When the outstanding reset token stops being accepted. */
  passwordResetExpiresAt?: Date;
  isLocked?: boolean;
  isOrgAdmin?: boolean;
  apiManagementAccess?: boolean;
  /**
   * This user's personal, permanent referral code — the one in the link they
   * share from Referral Management (`/register?ref=CODE`).
   *
   * Distinct from the per-row `referralCode` on a ReferralTracking document,
   * which identifies one referral rather than the referrer. Minted on first use
   * by services/referralCode and never regenerated, so a link that has already
   * been shared keeps working. Sparse: only users who have shared a link have one.
   */
  referralCode?: string;
  /**
   * The referral relationship captured at registration: the code this user
   * signed up with, and the referrer who owns it.
   *
   * Recorded once, at registration, and read later when the user makes their
   * first qualifying purchase. Absent for anyone who registered without a valid
   * referral code.
   */
  referredByCode?: string;
  referredByUserId?: mongoose.Types.ObjectId;
  createdAt: Date;
  updatedAt: Date;
  comparePassword(password: string): Promise<boolean>;
}

const UserSchema = new Schema<IUser>({
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true,
    trim: true,
    lowercase: true
  },
  name: {
    type: String,
    required: [true, 'Name is required'],
    trim: true,
    maxlength: [100, 'Name cannot exceed 100 characters']
  },
  role: {
    type: String,
    enum: ['super-admin', 'admin', 'manager', 'editor', 'viewer'],
    default: 'viewer'
  },
  username: {
    type: String,
    trim: true,
    sparse: true,
    unique: true,
    minlength: [3, 'Username must be at least 3 characters'],
    maxlength: [30, 'Username cannot exceed 30 characters'],
  },
  phone: {
    type: String,
    trim: true,
  },
  status: {
    type: String,
    enum: ['active', 'inactive', 'suspended'],
    default: 'active',
  },
  profileImage: {
    type: String,
  },
  notes: {
    type: String,
    maxlength: [1000, 'Notes cannot exceed 1000 characters'],
  },
  lastLoginAt: {
    type: Date,
  },
  roleId: {
    type: Schema.Types.ObjectId,
    ref: 'Role',
  },
  mustChangePassword: {
    type: Boolean,
    default: false,
  },
  // Password reset — only the hash of the emailed token is ever stored, and the
  // toJSON transform strips both fields so they never reach a response body.
  passwordResetTokenHash: {
    type: String,
  },
  passwordResetExpiresAt: {
    type: Date,
  },
  isLocked: {
    type: Boolean,
    default: false,
  },
  isOrgAdmin: {
    type: Boolean,
    default: false,
  },
  apiManagementAccess: {
    type: Boolean,
    default: false,
  },
  // Sparse unique: only minted when the user first shares a referral link, and
  // unique so two users can never hand out the same code.
  referralCode: {
    type: String,
    trim: true,
    uppercase: true,
    unique: true,
    sparse: true,
  },
  // Who referred this user, captured at registration. Not unique — one referrer
  // has many referred users.
  referredByCode: {
    type: String,
    trim: true,
    uppercase: true,
  },
  referredByUserId: {
    type: Schema.Types.ObjectId,
    ref: 'User',
  },
  companyIds: [{
    type: String,
    index: true
  }],
  activeCompanyId: {
    type: String,
    default: null
  },
  avatar: {
    type: String
  },
  passwordHash: {
    type: String,
    required: [true, 'Password hash is required']
  },
  panelSettings: {
    type: Schema.Types.Mixed,
    default: {}
  },
  userSettings: {
    type: Schema.Types.Mixed,
    default: {
      useGlobalAIConfig: true,
      aiConfig: null,
      preferredTextModel: null,
      rememberTextModelChoice: false,
      aiModelPreferences: {
        text: { model: null, remember: false },
        image: { model: null, remember: false },
        video: { model: null, remember: false },
        audio: { model: null, remember: false },
      },
    }
  }
}, {
  timestamps: true,
  toJSON: {
    transform: (doc, ret) => {
      delete (ret as any).passwordHash;
      delete (ret as any).passwordResetTokenHash;
      delete (ret as any).passwordResetExpiresAt;
      return ret;
    }
  }
});

// Indexes
UserSchema.index({ email: 1 }, { unique: true });
UserSchema.index({ companyIds: 1 });
// Reset-token lookups hit this directly; sparse so accounts without a pending
// reset (the overwhelming majority) stay out of the index.
UserSchema.index({ passwordResetTokenHash: 1 }, { sparse: true });

// Hash password before saving
UserSchema.pre('save', async function(next) {
  if (!this.isModified('passwordHash')) return next();

  // Only hash if it's not already hashed (starts with $2a$)
  if (!this.passwordHash.startsWith('$2a$')) {
    const salt = await bcrypt.genSalt(12);
    this.passwordHash = await bcrypt.hash(this.passwordHash, salt);
  }
  next();
});

// Prevent more than one super-admin
UserSchema.pre('save', async function(next) {
  if (this.role === 'super-admin' && this.isNew) {
    const existingSuperAdmin = await (this.constructor as any).findOne({ role: 'super-admin', _id: { $ne: this._id } });
    if (existingSuperAdmin) {
      const err = new Error('A Super Admin account already exists. Only one Super Admin is allowed.');
      (err as any).name = 'ValidationError';
      return next(err);
    }
  }
  next();
});

// Prevent changing role to super-admin if one already exists
UserSchema.pre('save', async function(next) {
  if (this.isModified('role') && this.role === 'super-admin' && !this.isNew) {
    const existingSuperAdmin = await (this.constructor as any).findOne({ role: 'super-admin', _id: { $ne: this._id } });
    if (existingSuperAdmin) {
      const err = new Error('Cannot assign Super Admin role. A Super Admin account already exists.');
      (err as any).name = 'ValidationError';
      return next(err);
    }
  }
  next();
});

// Compare password method
UserSchema.methods.comparePassword = async function(password: string): Promise<boolean> {
  return bcrypt.compare(password, this.passwordHash);
};

export const User = mongoose.models.User || mongoose.model<IUser>('User', UserSchema);