import mongoose from 'mongoose';
const { Schema } = mongoose;

import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';

const userSchema = new Schema({
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  roles: { type: [String], default: ['user'] },
}, { timestamps: true });

const User = mongoose.model('User', userSchema);

const add = async ({ username, password } = {}) => {
  return User.findOne({ username })
  .then(existing => {
    if (existing) {
      return Promise.reject(new Error('USER_EXISTS'));
    }
    return bcrypt.hash(password, 10);
  })
  .then(hashedPassword => {
    const user = new User({ username, password: hashedPassword });
    return user.save();
  });
}

const login = async ({ username, password } = {}) => {
  return User.findOne({ username })
  .then(user => {
    if (!user) {
      return Promise.reject(new Error('USER_NOT_FOUND'));
    }
    return Promise.all([
      bcrypt.compare(password, user.password),
      Promise.resolve(user),
    ]);
  })
  .then(([isMatch, user]) => {
    if (!isMatch) {
      return Promise.reject(new Error('PASSWORD_ERROR'));
    }
    return user;
  })
  .then(user => {
    // 签发 Access Token 和 Refresh Token
    const accessToken = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '30m' });
    const refreshToken = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });

    const userInfo = {
      uid: user._id.toString(),
      username: user.username,
      roles: user.roles ?? ['user'],
    }

    return { info: userInfo, accessToken, refreshToken };
  });
}

const refresh = async (refreshToken) => {
  const decoded = jwt.verify(refreshToken, process.env.JWT_SECRET);
  return User.findById(decoded.userId)
  .then(user => {
    if (!user) {
      return Promise.reject(new Error('USER_NOT_FOUND'));
    }

    // 签发新的 Access Token
    const newAccessToken = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '30m' });
    return newAccessToken;
  });
}

const info = async (userId) => {
  return User.findById(userId).select('-password')
  .then(user => {
    if (!user) {
      return Promise.reject(new Error('USER_NOT_FOUND'));
    }
    return {
      uid: user._id.toString(),
      username: user.username,
      roles: user.roles ?? ['user'],
    };
  });
}

const init = async ({ username, password } = {}) => {
  return User.find()
  .then(users => {
    if (users.length) {
      return Promise.reject('USER_EXISTS');
    }
    return bcrypt.hash(password, 10);
  })
  .then(hashedPassword => {
    const user = new User({
      username,
      password: hashedPassword,
      roles: ['admin'],
    });
    return user.save();
  });
}

export { add, login, refresh, info, init };
export default { add, login, refresh, info, init };
