import { hash } from "@node-rs/argon2";
import { passwordStrength } from "check-password-strength";
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import * as uuid from 'uuid';
import { config } from '../../../config';
import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors';
import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors';
import { User } from '../../entities/User';
import { UserGroup } from '../../entities/UserGroup';

/**
 * This classed is used to create a new User entity from a json body (post request).
 */
export class CreateUser {
    /**
     * The new user's first name.
     */
    @IsString()
    firstname: string;

    /**
     * The new user's middle name.
     */
    @IsString()
    @IsOptional()
    middlename?: string;

    /**
     * The new user's last name.
     */
    @IsString()
    lastname: string;

    /**
     * The new user's username.
     * You have to provide a email addres, so this is optional.
     */
    @IsOptional()
    @IsString()
    username?: string;

    /**
     * The new user's email address.
     */
    @IsEmail()
    @IsString()
    @IsNotEmpty()
    email: string;

    /**
     * The new user's phone number.
     * This will be validated against the configured country phone numer syntax (default: international).
     */
    @IsPhoneNumber(config.phone_validation_countrycode)
    @IsOptional()
    phone?: string;

    /**
     * The new user's password.
     * This will of course not be saved in plaintext :)
     */
    @IsString()
    password: string;

    /**
     * Will the new user be enabled from the start?
     * Default: true
     */
    @IsBoolean()
    @IsOptional()
    enabled?: boolean = true;

    /**
     * The new user's groups' ids.
     * You can provide either one groupId or an array of groupIDs.
     */
    @IsOptional()
    groups?: number[] | number

    /**
    * The user's profile pic (or rather a url pointing to it).
    */
    @IsString()
    @IsUrl()
    @IsOptional()
    profilePic?: string;

    /**
     * Converts this to a User entity.
     */
    public async toEntity(): Promise<User> {
        let newUser: User = new User();

        if (!this.email) {
            throw new UserEmailNeededError();
        }
        if (this.username?.includes("@")) { throw new UsernameContainsIllegalCharacterError(); }

        let password_strength = passwordStrength(this.password);
        if (!password_strength.contains.includes("uppercase")) { throw new PasswordMustContainUppercaseLetterError(); }
        if (!password_strength.contains.includes("lowercase")) { throw new PasswordMustContainLowercaseLetterError(); }
        if (!password_strength.contains.includes("number")) { throw new PasswordMustContainNumberError(); }
        if (!(password_strength.length > 9)) { throw new PasswordTooShortError(); }

        newUser.email = this.email
        newUser.username = this.username
        newUser.firstname = this.firstname
        newUser.middlename = this.middlename
        newUser.lastname = this.lastname
        newUser.uuid = uuid.v4()
        newUser.phone = this.phone
        newUser.password = await hash(this.password + newUser.uuid);
        newUser.groups = await this.getGroups();
        newUser.enabled = this.enabled;

        if (!this.profilePic) { newUser.profilePic = `https://lauf-fuer-kaya.de/lfk-logo.png`; }
        else { newUser.profilePic = this.profilePic; }

        return newUser;
    }

    /**
     * Get's all groups for this user by their id's;
     */
    public async getGroups() {
        if (!this.groups) { return null; }
        let groups = new Array<UserGroup>();
        if (!Array.isArray(this.groups)) {
            this.groups = [this.groups]
        }
        for (let group of this.groups) {
            let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
            if (!found) { throw new UserGroupNotFoundError(); }
            groups.push(found);
        }
        return groups;
    }
}