import {
  Table,
  Column,
  AllowNull,
  Length,
  HasMany,
  AfterSave,
  AfterDestroy,
  BeforeValidate,
  BeforeSave,
  BeforeDestroy,
} from "sequelize-typescript";
import * as UUID from "uuid";
import {
  ActionPermissionTopic,
  ActionPermissionMode,
  Permission,
  PermissionTopics,
} from "./Permission";
import { LockableHelper } from "../modules/lockableHelper";
import { APIData } from "../modules/apiData";
import { CommonModel } from "../classes/commonModel";

@Table({ tableName: "apiKeys", paranoid: false })
export class ApiKey extends CommonModel<ApiKey> {
  idPrefix() {
    return "key";
  }

  @AllowNull(false)
  @Length({ min: 3, max: 191 })
  @Column
  name: string;

  @AllowNull(false)
  @Column
  apiKey: string;

  @Column
  locked: string;

  @Column
  permissionAllRead: boolean;

  @Column
  permissionAllWrite: boolean;

  @HasMany(() => Permission, {
    foreignKey: "ownerId",
    scope: { ownerType: "apiKey" },
  })
  permissions: Permission[];

  @BeforeValidate
  static async buildApiKey(instance: ApiKey) {
    if (!instance.apiKey) {
      instance.apiKey = UUID.v4()
        .replace(/-/g, "")
        .replace(/_/g, "")
        .toLowerCase();
    }
  }

  async apiData() {
    const permissions = await this.$get("permissions", {
      order: [["topic", "asc"]],
    });
    const permissionsApiData = await Promise.all(
      permissions.map((prm) => prm.apiData())
    );

    return {
      id: this.id,
      name: this.name,
      apiKey: this.apiKey,
      locked: this.locked,
      permissionAllRead: this.permissionAllRead,
      permissionAllWrite: this.permissionAllWrite,
      permissions: permissionsApiData,
      createdAt: APIData.formatDate(this.createdAt),
      updatedAt: APIData.formatDate(this.updatedAt),
    };
  }

  async authorizeAction(
    topic: ActionPermissionTopic,
    mode: ActionPermissionMode
  ) {
    return Permission.authorizeAction(topic, mode, this);
  }

  async setPermissions(
    userPermissions: { topic: string; read: boolean; write: boolean }[]
  ) {
    const permissions = await this.$get("permissions");
    for (const userPermission of userPermissions) {
      const permission = permissions.find(
        (p) => p.topic === userPermission.topic
      );
      if (!permission) {
        throw new Error(
          `Cannot find permission for topic ${userPermission.topic}`
        );
      }

      permission.read =
        this.permissionAllRead !== null
          ? this.permissionAllRead
          : userPermission.read;
      permission.write =
        this.permissionAllWrite !== null
          ? this.permissionAllWrite
          : userPermission.write;

      await LockableHelper.beforeSave(permission);
      await permission.save();
    }
  }

  // --- Class Methods --- //

  @BeforeSave
  static async noUpdateIfLocked(instance: ApiKey) {
    await LockableHelper.beforeSave(instance);
  }

  @AfterSave
  static async buildPermissions(instance: ApiKey) {
    for (const i in PermissionTopics) {
      const topic = PermissionTopics[i];
      let permission = await Permission.findOne({
        where: {
          topic,
          ownerId: instance.id,
          ownerType: "apiKey",
        },
      });

      if (!permission) {
        permission = await Permission.create({
          topic,
          ownerId: instance.id,
          ownerType: "apiKey",
        });
      }

      if (instance.permissionAllRead !== null) {
        await permission.update({ read: instance.permissionAllRead });
      }
      if (instance.permissionAllWrite !== null) {
        await permission.update({ write: instance.permissionAllWrite });
      }
      if (instance.locked && !permission.locked) {
        await permission.update({ locked: instance.locked });
      }
    }
  }

  @BeforeDestroy
  static async noDestroyIfLocked(instance: ApiKey) {
    await LockableHelper.beforeDestroy(instance);
  }

  @AfterDestroy
  static async deletePermissions(instance: ApiKey, {}) {
    return Permission.destroy({
      where: { ownerId: instance.id },
    });
  }
}
