import { Schema, model, Model, models } from 'mongoose';
import {
  hashPassword,
  verifyPassword,
} from '../utils/helpers/hashPasswordHelpers';
import { IUser } from '../utils/interface/userInterface';
const validator = require('validator');

interface UserModel extends Model<IUser, {}, {}> {
  findByCredentials(username: string, password: string): object;
}

const UserSchema = new Schema<IUser, UserModel>(
  {
    fullName: {
      type: String,
      required: [true, 'Please provide a name.'],
      maxlength: [60, 'Name cannot be more than 60 characters'],
      trim: true,
    },
    username: {
      type: String,
      required: [true, 'Please provide an username.'],
      unique: true,
      maxlength: [60, 'Name cannot be more than 60 characters'],
      trim: true,
    },
    email: {
      type: String,
      unique: true,
      required: [true, 'Please provide an email.'],
      trim: true,
      lowercase: true,
      validate(value: string) {
        if (!validator.isEmail(value)) {
          throw new Error('Email is invalid');
        }
      },
    },
    password: {
      type: String,
      required: [true, 'Please provide a password.'],
      minlength: 7,
      trim: true,
      validate(value: string) {
        if (value.toLowerCase().includes('password')) {
          throw new Error('Password cannot contain "password"');
        }
      },
    },
    country: {
      type: String,
      required: [true, 'Please provide a country.'],
      trim: true,
    },
    city: {
      type: String,
      required: [true, 'Please provide a city.'],
      trim: true,
    },
    address: {
      type: String,
      required: [true, 'Please provide an address.'],
      trim: true,
    },
    address2: {
      type: String,
      trim: true,
    },
    postcode: {
      type: String,
      required: [true, 'Please provide a postal code.'],
    },
  },
  {
    toJSON: { virtuals: true }, // So `res.json()` and other `JSON.stringify()` functions include virtuals
    toObject: { virtuals: true }, // So `console.log()` and other functions that use `toObject()` include virtuals
  }
);

UserSchema.virtual('orders', {
  ref: 'Order',
  localField: '_id',
  foreignField: 'owner',
});

UserSchema.static(
  'findByCredentials',
  async function findByCredentials(username: string, password: string) {
    const user = await User.findOne({ username });

    if (!user) {
      throw new Error('Unable to login');
    }

    const isMatch = await verifyPassword(password, user.password);

    if (!isMatch) {
      throw new Error('Unable to login');
    }

    const userData = {
      fullName: user.fullName,
      email: user.email,
      address: user.address,
      address2: user.address2,
      city: user.city,
      country: user.country,
      postcode: user.postcode,
      orders: user.orders,
      _id: user._id,
    };
    return userData;
  }
);

UserSchema.pre('save', async function (next) {
  const user = this;

  if (user.isModified('password')) {
    user.password = await hashPassword(user.password);
  }

  next();
});

const User = models.User || model<IUser, UserModel>('User', UserSchema, 'User');
module.exports = User;
