import { User } from '../model/user.model';
import { UserDao } from '../model/persistence/userDao'
import { CommonValidations } from './commonValidations';
import { InvalidEmailError, InvalidPasswordError, InvalidRoleError } from './customErrors';
import * as bcrypt from 'bcrypt';
import * as jwt from 'jsonwebtoken';
import { Role } from '../model/role.model';

const userDAO = new UserDao();
const commonValidations = new CommonValidations();

export class AuthController {

    /**
     * Hashes the password and saves the user.
     * @param json User object in JSON representation.
     * @returns Newly registered user.
     */
    async registerUser(json: object) {
        let user = new User;
        user = Object.assign(user, json);

        // Validate fields
        commonValidations.throwIsEmptyOrNullErrorIfIs(user.email);
        this.throwInvalidEmailErrorIfInvalid(user.email);
        this.throwInvalidPasswordErrorIfInsecure(user.password);
        commonValidations.throwIsEmptyOrNullErrorIfIs(user.username);
        commonValidations.throwIsNullErrorIfIs(user.role);
        this.throwInvalidRoleErrorIfIs(user.role);

        // Standardize email
        user.email = user.email.toLowerCase();

        // Hash password and save
        const hashedPassword = await bcrypt.hash(user.password, 10);
        user.password = hashedPassword;
        return await userDAO.addUser(user);
    }

    /**
     * Verifies login credentials and returns JWT token if credebtials are valid.
     * @param json User object in JSON representation.
     * @returns  
     */
    async loginUser(json: object) : Promise<string> {
        let requestUser = new User;
        requestUser = Object.assign(requestUser, json);

        // Validate fields
        commonValidations.throwIsEmptyOrNullErrorIfIs(requestUser.email);
        
        // Validate username & passwort
        const savedUser = await userDAO.getUser(requestUser.email.toLowerCase());
        const passwordMatch = await bcrypt.compare(requestUser.password, savedUser.password);
        if (!passwordMatch) {
            throw new InvalidPasswordError('Invalid password.');
        }

        // Generate JWT token
        const token = jwt.sign({ email: savedUser.email, role: savedUser.role }, process.env.JWTSECRET, { expiresIn: '1h'});
        return token;
    }

    /**
     * Throws an [InvalidPasswordError] if [password] does not match the security minimum.
     * Minimum includes: At least 8 characters long, and at least: 1 uppercase letter, 1 lowercase letter, 1 digit, 1 special character
     * @param password The password to be validated.
     */
    throwInvalidPasswordErrorIfInsecure(password: string) {
        const insufficientLength = (password.length < 8);
        const containsLowercase = /[a-z]/.test(password);
        const containsUppercase = /[A-Z]/.test(password);
        const containsNumbers = /\d/.test(password);
        const containsSpecialchars = /[^A-Za-z0-9]/.test(password);
        if (insufficientLength || !containsLowercase || !containsUppercase || !containsNumbers || !containsSpecialchars) throw new InvalidPasswordError('Password does not comply with minimum requirements.');
    }

    /**
     * Throws an [InvalidEmailError] if the given email address is not a valid OCCOA email address.
     * @param email The email address to verify.
     */
    throwInvalidEmailErrorIfInvalid(email: string) {
        const isValidOccoaEmail = /^[^@\s]+@oceanacoa\.com$/.test(email);
        if (!isValidOccoaEmail) throw new InvalidEmailError('Invalid OCCOA email address.');
    }

    /**
     * Throws an [InvalidRoleError] if the given role is not a valid role.
     * @param role The role to verify.
     */
    throwInvalidRoleErrorIfIs(role: Role) {
        if (!Object.values(Role).includes(role)) throw new InvalidRoleError('Invalid role.');
    }
}