import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl, IsUUID } from "class-validator";
import { ChildEntity, Column, Index, JoinTable, ManyToMany, OneToMany } from "typeorm";
import { config } from '../../config';
import { ResponsePrincipal } from '../responses/ResponsePrincipal';
import { ResponseUser } from '../responses/ResponseUser';
import { Permission } from './Permission';
import { Principal } from './Principal';
import type { UserAction } from './UserAction';
import { UserGroup } from './UserGroup';

/**
 * Defines the User entity.
 * Users are the ones that can use the "admin" webui and do stuff in the backend.
 */
@ChildEntity()
@Index(['enabled'])
export class User extends Principal {
  /**
  * The user's uuid.
  * Mainly gets used as a per-user salt for the password hash.
  */
  @Column({ unique: true })
  @IsUUID(4)
  uuid: string;

  /**
  * The user's e-mail address.
  * Either username or email has to be set (otherwise the user couldn't log in).
  */
  @Column({ nullable: false, unique: true })
  @IsEmail()
  @IsNotEmpty()
  email: string;

  /**
  * The user's phone number.
  */
  @Column({ nullable: true })
  @IsOptional()
  @IsPhoneNumber(config.phone_validation_countrycode)
  phone?: string;

  /**
  * The user's username.
  * Either username or email has to be set (otherwise the user couldn't log in).
  */
  @Column({ nullable: true, unique: true })
  @IsString()
  username?: string;

  /**
  * The user's first name.
  */
  @Column()
  @IsString()
  @IsNotEmpty()
  firstname: string;

  /**
  * The user's middle name.
  */
  @Column({ nullable: true })
  @IsString()
  @IsOptional()
  middlename?: string;

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

  /**
  * The user's password.
  * This is a argon2 hash salted with the user's uuid.
  */
  @Column()
  @IsString()
  @IsNotEmpty()
  password: string;

  /**
  * The groups this user is a part of.
  * The user will inherit the groups permissions (without overwriting his own).
  */
  @IsOptional()
  @ManyToMany(() => UserGroup, { nullable: true })
  @JoinTable()
  groups: UserGroup[];

  /**
  * Is this user enabled?
  */
  @Column()
  @IsBoolean()
  enabled: boolean = true;

  /**
  * The user's jwt refresh token count.
  * Used to invalidate jwts.
  */
  @IsInt()
  @Column({ default: 1 })
  refreshTokenCount?: number;

  /**
  * The user's profile picture.
  * We haven't decided yet if this will be a bas64 encoded image or just a link to the profile picture.
  */
  @Column({ nullable: false, unique: false })
  @IsString()
  @IsUrl()
  profilePic: string;

  /**
  * The last time the user requested a password reset.
  * Used to prevent spamming of the password reset route.
  */
  @Column({ nullable: true, unique: false })
  @IsString()
  @IsOptional()
  resetRequestedTimestamp?: number;

  /**
   * The actions performed by this user.
   * For documentation purposes only, will be implemented later.
   */
  @IsOptional()
  @OneToMany(() => require("./UserAction").UserAction, (action: UserAction) => action.user, { nullable: true })
  actions!: UserAction[]

  /**
   * Resolves all permissions granted to this user through groups.
   */
  public get inheritedPermissions(): Permission[] {
    let returnPermissions: Permission[] = new Array<Permission>();

    if (!this.groups) { return returnPermissions; }
    for (let group of this.groups) {
      if (group.permissions) {
        for (let permission of group.permissions) {
          returnPermissions.push(permission);
        }
      }
    }
    return returnPermissions;
  }

  /**
   * Resolves all permissions granted to this user through groups or directly to the string enum format.
   * Also deduplicates the array.
   */
  public get allPermissions(): string[] {
    let returnPermissions: string[] = new Array<string>();

    if (!this.permissions) { return returnPermissions; }
    for (let permission of this.permissions) {
      returnPermissions.push(permission.toString());
    }

    if (!this.groups) { return returnPermissions; }
    for (let group of this.groups) {
      if (group.permissions) {
        for (let permission of group.permissions) {
          returnPermissions.push(permission.toString());
        }
      }
    }
    return Array.from(new Set(returnPermissions));
  }

  /**
   * Turns this entity into it's response class.
   */
  public toResponse(): ResponsePrincipal {
    return new ResponseUser(this);
  }
}
