const { DataTypes } = require("sequelize");
const sequelize = require("../config/database");
const bcrypt = require("bcryptjs");

const User = sequelize.define(
  "User",
  {
    id: {
      type: DataTypes.UUID,
      defaultValue: DataTypes.UUIDV4,
      primaryKey: true,
    },
    email: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false,
      validate: {
        isEmail: true,
      },
    },
    password: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    firstName: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    lastName: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    phone: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    authProvider: {
      type: DataTypes.ENUM("local", "google"),
      defaultValue: "local",
    },
    providerId: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    address1: {
      type: DataTypes.STRING,
    },
    address2: {
      type: DataTypes.STRING,
    },
    city: {
      type: DataTypes.STRING,
    },
    state: {
      type: DataTypes.STRING,
    },
    zipCode: {
      type: DataTypes.STRING,
    },
    country: {
      type: DataTypes.STRING,
    },
    imageFilename: {
      type: DataTypes.TEXT,
    },
    isVerified: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    },
    verificationToken: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    verificationTokenExpiry: {
      type: DataTypes.DATE,
      allowNull: true,
    },
    verifiedAt: {
      type: DataTypes.DATE,
      allowNull: true,
    },
    passwordResetToken: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    passwordResetTokenExpiry: {
      type: DataTypes.DATE,
      allowNull: true,
    },
    defaultAvailableAfter: {
      type: DataTypes.STRING,
      defaultValue: "00:00",
    },
    defaultAvailableBefore: {
      type: DataTypes.STRING,
      defaultValue: "23:00",
    },
    defaultSpecifyTimesPerDay: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    },
    defaultWeeklyTimes: {
      type: DataTypes.JSONB,
      defaultValue: {
        sunday: { availableAfter: "00:00", availableBefore: "23:00" },
        monday: { availableAfter: "00:00", availableBefore: "23:00" },
        tuesday: { availableAfter: "00:00", availableBefore: "23:00" },
        wednesday: { availableAfter: "00:00", availableBefore: "23:00" },
        thursday: { availableAfter: "00:00", availableBefore: "23:00" },
        friday: { availableAfter: "00:00", availableBefore: "23:00" },
        saturday: { availableAfter: "00:00", availableBefore: "23:00" },
      },
    },
    stripeConnectedAccountId: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    stripePayoutsEnabled: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
      allowNull: true,
    },
    stripeCustomerId: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    stripeRequirementsCurrentlyDue: {
      type: DataTypes.JSON,
      defaultValue: [],
      allowNull: true,
    },
    stripeRequirementsPastDue: {
      type: DataTypes.JSON,
      defaultValue: [],
      allowNull: true,
    },
    stripeDisabledReason: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    stripeRequirementsLastUpdated: {
      type: DataTypes.DATE,
      allowNull: true,
    },
    loginAttempts: {
      type: DataTypes.INTEGER,
      defaultValue: 0,
    },
    lockUntil: {
      type: DataTypes.DATE,
      allowNull: true,
    },
    jwtVersion: {
      type: DataTypes.INTEGER,
      defaultValue: 0,
      allowNull: false,
    },
    role: {
      type: DataTypes.ENUM("user", "admin"),
      defaultValue: "user",
      allowNull: false,
    },
    isBanned: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
      allowNull: false,
    },
    bannedAt: {
      type: DataTypes.DATE,
      allowNull: true,
    },
    bannedBy: {
      type: DataTypes.UUID,
      allowNull: true,
    },
    banReason: {
      type: DataTypes.TEXT,
      allowNull: true,
    },
    itemRequestNotificationRadius: {
      type: DataTypes.INTEGER,
      defaultValue: 10,
      allowNull: true,
      validate: {
        min: 1,
        max: 100,
      },
    },
    verificationAttempts: {
      type: DataTypes.INTEGER,
      defaultValue: 0,
      allowNull: true,
    },
  },
  {
    hooks: {
      beforeCreate: async (user) => {
        if (user.password) {
          user.password = await bcrypt.hash(user.password, 12);
        }
      },
      beforeUpdate: async (user) => {
        if (user.changed("password") && user.password) {
          user.password = await bcrypt.hash(user.password, 12);
        }
      },
    },
  }
);

User.prototype.comparePassword = async function (password) {
  if (!this.password) {
    return false;
  }
  return bcrypt.compare(password, this.password);
};

// Account lockout constants
const MAX_LOGIN_ATTEMPTS = 10;
const LOCK_TIME = 2 * 60 * 60 * 1000; // 2 hours

// Check if account is locked
User.prototype.isLocked = function () {
  return !!(this.lockUntil && this.lockUntil > Date.now());
};

// Increment login attempts and lock account if necessary
User.prototype.incLoginAttempts = async function () {
  // If we have a previous lock that has expired, restart at 1
  if (this.lockUntil && this.lockUntil < Date.now()) {
    return this.update({
      loginAttempts: 1,
      lockUntil: null,
    });
  }

  const updates = { loginAttempts: this.loginAttempts + 1 };

  // Lock account after max attempts
  if (this.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !this.isLocked()) {
    updates.lockUntil = Date.now() + LOCK_TIME;
  }

  return this.update(updates);
};

// Reset login attempts after successful login
User.prototype.resetLoginAttempts = async function () {
  return this.update({
    loginAttempts: 0,
    lockUntil: null,
  });
};

// Email verification methods
// Maximum verification attempts before requiring a new code
const MAX_VERIFICATION_ATTEMPTS = 5;

User.prototype.generateVerificationToken = async function () {
  const crypto = require("crypto");
  // Generate 6-digit numeric code (100000-999999)
  const code = crypto.randomInt(100000, 999999).toString();
  const expiry = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours

  return this.update({
    verificationToken: code,
    verificationTokenExpiry: expiry,
    verificationAttempts: 0, // Reset attempts on new code
  });
};

User.prototype.isVerificationTokenValid = function (token) {
  const crypto = require("crypto");

  if (!this.verificationToken || !this.verificationTokenExpiry) {
    return false;
  }

  // Check if token is expired
  if (new Date() > new Date(this.verificationTokenExpiry)) {
    return false;
  }

  // Validate 6-digit format
  if (!/^\d{6}$/.test(token)) {
    return false;
  }

  // Use timing-safe comparison to prevent timing attacks
  try {
    const inputBuffer = Buffer.from(token);
    const storedBuffer = Buffer.from(this.verificationToken);

    if (inputBuffer.length !== storedBuffer.length) {
      return false;
    }

    return crypto.timingSafeEqual(inputBuffer, storedBuffer);
  } catch {
    return false;
  }
};

// Check if too many verification attempts
User.prototype.isVerificationLocked = function () {
  return (this.verificationAttempts || 0) >= MAX_VERIFICATION_ATTEMPTS;
};

// Increment verification attempts
User.prototype.incrementVerificationAttempts = async function () {
  const newAttempts = (this.verificationAttempts || 0) + 1;
  await this.update({ verificationAttempts: newAttempts });
  return newAttempts;
};

User.prototype.verifyEmail = async function () {
  return this.update({
    isVerified: true,
    verifiedAt: new Date(),
    verificationToken: null,
    verificationTokenExpiry: null,
    verificationAttempts: 0,
  });
};

// Password reset methods
User.prototype.generatePasswordResetToken = async function () {
  const crypto = require("crypto");
  // Generate random token for email URL
  const token = crypto.randomBytes(32).toString("hex");
  // Hash token before storing in database (SHA-256)
  const hashedToken = crypto.createHash("sha256").update(token).digest("hex");
  const expiry = new Date(Date.now() + 60 * 60 * 1000); // 1 hour

  await this.update({
    passwordResetToken: hashedToken,
    passwordResetTokenExpiry: expiry,
  });

  // Return plain token for email URL (not stored in DB)
  return token;
};

User.prototype.isPasswordResetTokenValid = function (token) {
  if (!this.passwordResetToken || !this.passwordResetTokenExpiry) {
    return false;
  }

  // Check if token is expired first
  if (new Date() > new Date(this.passwordResetTokenExpiry)) {
    return false;
  }

  const crypto = require("crypto");

  // Hash the incoming token to compare with stored hash
  const hashedToken = crypto.createHash("sha256").update(token).digest("hex");

  // Use timing-safe comparison to prevent timing attacks
  const storedTokenBuffer = Buffer.from(this.passwordResetToken, "hex");
  const hashedTokenBuffer = Buffer.from(hashedToken, "hex");

  // Ensure buffers are same length for timingSafeEqual
  if (storedTokenBuffer.length !== hashedTokenBuffer.length) {
    return false;
  }

  return crypto.timingSafeEqual(storedTokenBuffer, hashedTokenBuffer);
};

User.prototype.resetPassword = async function (newPassword) {
  return this.update({
    password: newPassword,
    passwordResetToken: null,
    passwordResetTokenExpiry: null,
    // Increment JWT version to invalidate all existing sessions
    jwtVersion: this.jwtVersion + 1,
  });
};

// Ban user method - sets ban fields and invalidates all sessions
User.prototype.banUser = async function (adminId, reason) {
  return this.update({
    isBanned: true,
    bannedAt: new Date(),
    bannedBy: adminId,
    banReason: reason,
    // Increment JWT version to immediately invalidate all sessions
    jwtVersion: this.jwtVersion + 1,
  });
};

// Unban user method - clears ban fields
User.prototype.unbanUser = async function () {
  return this.update({
    isBanned: false,
    bannedAt: null,
    bannedBy: null,
    banReason: null,
    // Note: We don't increment jwtVersion on unban - user will need to log in fresh
  });
};

module.exports = User;
