/**
 * User Model
 * Represents system users (cashiers, managers, system admins)
 */

// Import bcrypt for password hashing
const bcrypt = require('bcryptjs');

module.exports = (sequelize, DataTypes) => {
  // Define User model
  const User = sequelize.define('User', {
    // Primary key
    id: {
      type: DataTypes.BIGINT, // Use BIGINT for large scale
      primaryKey: true, // Set as primary key
      autoIncrement: true, // Auto-increment ID
    },
    // Username (unique identifier for login)
    username: {
      type: DataTypes.STRING(100), // Username with max length
      allowNull: false, // Username is required
      unique: true, // Username must be unique
      validate: {
        notEmpty: true, // Username cannot be empty string
        len: [3, 100], // Username length validation (min 3 characters)
      },
    },
    // Email address
    email: {
      type: DataTypes.STRING(255), // Email with max length
      allowNull: true, // Email is optional
      unique: true, // Email must be unique if provided
      validate: {
        isEmail: true, // Validate email format if provided
        len: [0, 255], // Email length validation
      },
    },
    // Hashed password (never store plain text passwords)
    password: {
      type: DataTypes.STRING(255), // Hashed password with max length
      allowNull: false, // Password is required
      validate: {
        notEmpty: true, // Password cannot be empty string
        len: [6, 255], // Minimum password length validation
      },
    },
    // Full name of the user
    full_name: {
      type: DataTypes.STRING(150), // Full name with max length
      allowNull: false, // Full name is required
      validate: {
        notEmpty: true, // Full name cannot be empty string
        len: [1, 150], // Full name length validation
      },
    },
    // User role (cashier, manager, system_admin)
    role: {
      type: DataTypes.ENUM('cashier', 'manager', 'system_admin'), // Enum for user role
      allowNull: false, // Role is required
      defaultValue: 'cashier', // Default role is cashier
      validate: {
        isIn: [['cashier', 'manager', 'system_admin']], // Validate enum values
      },
    },
    // PIN code for quick POS login (optional, 4-6 digits)
    pin_code: {
      type: DataTypes.STRING(255), // PIN code (hashed - bcrypt hash is 60 chars, but using 255 for safety)
      allowNull: true, // PIN code is optional
      validate: {
        len: [0, 255], // PIN code length validation
      },
    },
    // Active status (soft delete)
    active: {
      type: DataTypes.BOOLEAN, // Boolean for active status
      defaultValue: true, // Default to active
      allowNull: false, // Active status is required
    },
    // Last login timestamp
    last_login: {
      type: DataTypes.DATE, // Last login date and time
      allowNull: true, // Last login is optional
    },
  }, {
    // Model options
    tableName: 'users', // Explicit table name
    underscored: true, // Use snake_case for database columns
    timestamps: true, // Enable createdAt and updatedAt timestamps
    createdAt: 'created_at', // Map createdAt to created_at column
    updatedAt: 'updated_at', // Map updatedAt to updated_at column
    hooks: {
      // Hash password before creating user
      beforeCreate: async (user) => {
        // Hash password if it's been changed and is not already hashed
        if (user.password && !user.password.startsWith('$2a$')) {
          const salt = await bcrypt.genSalt(10); // Generate salt with 10 rounds
          user.password = await bcrypt.hash(user.password, salt); // Hash the password
        }
        // Hash PIN code if provided
        if (user.pin_code && !user.pin_code.startsWith('$2a$')) {
          const salt = await bcrypt.genSalt(10); // Generate salt with 10 rounds
          user.pin_code = await bcrypt.hash(user.pin_code, salt); // Hash the PIN code
        }
      },
      // Hash password before updating user (if password was changed)
      beforeUpdate: async (user) => {
        // Hash password if it's been changed and is not already hashed
        if (user.changed('password') && user.password && !user.password.startsWith('$2a$')) {
          const salt = await bcrypt.genSalt(10); // Generate salt with 10 rounds
          user.password = await bcrypt.hash(user.password, salt); // Hash the password
        }
        // Hash PIN code if it's been changed
        if (user.changed('pin_code') && user.pin_code && !user.pin_code.startsWith('$2a$')) {
          const salt = await bcrypt.genSalt(10); // Generate salt with 10 rounds
          user.pin_code = await bcrypt.hash(user.pin_code, salt); // Hash the PIN code
        }
      },
    },
    indexes: [
      // Unique index on username (already enforced by unique constraint)
      {
        unique: true, // Unique index
        fields: ['username'], // Index on username field
      },
      // Index on email (unique where not null)
      {
        unique: true, // Unique index
        fields: ['email'], // Index on email field
        where: {
          email: {
            [sequelize.Sequelize.Op.ne]: null, // Only index non-null emails
          },
        },
      },
      // Index on role for filtering
      {
        fields: ['role'], // Index on role field
      },
      // Index on active status for filtering
      {
        fields: ['active'], // Index on active field
      },
    ],
  });

  // Instance method to compare password (for login)
  User.prototype.comparePassword = async function (candidatePassword) {
    // Compare candidate password with stored hashed password
    return await bcrypt.compare(candidatePassword, this.password);
  };

  // Instance method to compare PIN code (for quick POS login)
  User.prototype.comparePin = async function (candidatePin) {
    // If no PIN code is set, return false
    if (!this.pin_code) {
      return false;
    }
    // Compare candidate PIN with stored hashed PIN
    return await bcrypt.compare(candidatePin, this.pin_code);
  };

  // Instance method to return user data without password
  User.prototype.toJSON = function () {
    // Get default JSON representation
    const values = Object.assign({}, this.get());
    // Remove sensitive fields from JSON output
    delete values.password; // Remove password
    delete values.pin_code; // Remove PIN code
    // Return safe user object
    return values;
  };

  // Class method to check if user has permission
  User.hasPermission = (userRole, requiredRole) => {
    // Define role hierarchy (higher index = more permissions)
    const roleHierarchy = {
      cashier: 0, // Lowest permissions
      manager: 1, // Medium permissions
      system_admin: 2, // Highest permissions (all permissions)
    };

    // Get role levels
    const userLevel = roleHierarchy[userRole] || -1; // Default to -1 if role not found
    const requiredLevel = roleHierarchy[requiredRole] || -1; // Default to -1 if role not found

    // User has permission if their level is >= required level
    return userLevel >= requiredLevel;
  };

  // Define model associations
  User.associate = (models) => {
    // User has many SystemLogs (one-to-many relationship)
    User.hasMany(models.SystemLog, {
      foreignKey: 'user_id', // Foreign key in SystemLogs table
      as: 'systemLogs', // Alias for association
    });
  };

  // Return User model
  return User;
};

