import { Inject, Injectable, forwardRef } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { rbac_roles as RbacRolesModel } from '@prisma/client';
import { UsersService } from 'src/users/users.service';

export type Role = RbacRolesModel;
export type RoleName = {
  id: number,
  name: string
};
export type RoleDescription = {
  id: number,
  name: string,
  description: string
};
export enum ERole {
  SystemDeveloper = 'System Developer',
  IPWAdministrator = 'IPW Administrator',
  IPWAuditor = 'IPW Auditor',
  IPWContentManager = 'IPW Content Manager',
  IPWDataCapturer = 'IPW Data Capturer',
  IPWLeadAuditor = 'IPW Lead Auditor',
  MemberReports = 'Member Reports',
  MemberAdministrator = 'Member Administrator',//2
  EditEvaluationForms = 'Edit Evaluation Forms',//2
  ViewEvaluationForms = 'View Evaluation Forms',//2
  ViewStatusOnly = 'View Status Only',//2
  DocumentUploads = 'Document Uploads',//2
  // Public = 'Public', // TODO: check db is this is actually used anywhere, perhaps just a part of code igniter
};

@Injectable()
export class RolesService {
  constructor(
    private prisma: PrismaService,
    @Inject(forwardRef(() => UsersService))
    private usersService: UsersService,
  ) {}

  find(): Promise<Role[]> {
    return this.prisma.rbac_roles.findMany();
  }

  // Used to list all roles in the ui, the legend (update a user to see it)
  // 1 = ipw users, system admins, auditors
  // 2 = ipw members, member admins, etc
  findDescriptions(options = { user_class_id: 2 }): Promise<RoleDescription[]> {
    const query = {
      where: {
        id: { not: 1 },
        user_class_id: options.user_class_id
      },
      select: {
        id: true,
        name: true,
        description: true
      }
    }
    return this.prisma.rbac_roles.findMany(query);
  }

  // Used to list the roles for the selected user
  async findByUserId(user_id: number): Promise<Role[]> {
    const roles: Role[] = await this.prisma.$queryRaw`
      SELECT DISTINCT rr.id, rr.name, rr.description, rr.importance, rr.user_class_id
      FROM users u
      INNER JOIN users_has_group uhg
      ON uhg.user_id = u.user_id
      INNER JOIN rbac_users_has_roles ruhr
      ON ruhr.users_has_group_id = uhg.users_has_group_id
      INNER JOIN rbac_roles rr
      ON ruhr.roles_id = rr.id
      WHERE u.user_id = ${user_id}
      ORDER BY rr.importance DESC
    `;
    return roles;
  }

  async findIdsByUserId(user_id: number): Promise<number[]> {
    const roles: Role[] = await this.prisma.$queryRaw`
      SELECT DISTINCT rr.id, rr.importance
      FROM users u
      INNER JOIN users_has_group uhg
      ON uhg.user_id = u.user_id
      INNER JOIN rbac_users_has_roles ruhr
      ON ruhr.users_has_group_id = uhg.users_has_group_id
      INNER JOIN rbac_roles rr
      ON ruhr.roles_id = rr.id
      WHERE u.user_id = ${user_id}
      ORDER BY rr.importance DESC
    `;
    return roles.map(r => r.id);
  }

  async findNamesByUserId(user_id: number): Promise<RoleName[]> {
    const roles: RoleName[] = await this.prisma.$queryRaw`
      SELECT DISTINCT rr.id, rr.name, rr.importance
      FROM users u
      INNER JOIN users_has_group uhg
      ON uhg.user_id = u.user_id
      INNER JOIN rbac_users_has_roles ruhr
      ON ruhr.users_has_group_id = uhg.users_has_group_id
      INNER JOIN rbac_roles rr
      ON ruhr.roles_id = rr.id
      WHERE u.user_id = ${user_id}
      ORDER BY rr.importance DESC
    `;
    return roles;
  }

  async findUserRoleDescriptions(user_id: number): Promise<RoleDescription[]> {
    const roles: Role[] = await this.prisma.$queryRaw`
      SELECT DISTINCT rr.id, rr.name, rr.description, rr.importance
      FROM users u
      INNER JOIN users_has_group uhg
      ON uhg.user_id = u.user_id
      INNER JOIN rbac_users_has_roles ruhr
      ON ruhr.users_has_group_id = uhg.users_has_group_id
      INNER JOIN rbac_roles rr
      ON ruhr.roles_id = rr.id
      WHERE u.user_id = ${user_id}
      ORDER BY rr.importance DESC
    `;
    return roles;
  }

  /*
   * @description Determine is a user had administrative priviledges for IPW
   * @return a boolean result
   */
  private ipwManagerRoles = [2,5];
  isIpwManager(role_id: number): boolean {
    let result = false;
    for (let i = 0, n = this.ipwManagerRoles.length; i < n; i++) {
      const testId = this.ipwManagerRoles[i];
      if (testId === role_id) {
        result = true;
        break;
      };
    }
    return result;
  }

  /*
   * @description Check to see if A user has joined a group
   * @return the record id of the relationship
   */
  async userHasGroup(user_group_id: number, user_id: number): Promise<number | null> {
    const result = await this.prisma.users_has_group.findFirst({
      where: {
        user_group_id,
        user_id
      },
      select: {
        users_has_group_id: true
      }
    });
    if (!result) return null;
    return result.users_has_group_id;
  }

  /*
   * @description Determine which roles the user has for a specific group
   * @return a list of roles or an empty array
   */
  async findGroupMemberRole(user_group_id: number, user_id: number): Promise<RoleName | null> {
    // Ensure that the user has joined the group
    const users_has_group_id = await this.userHasGroup(user_group_id, user_id);
    if (!users_has_group_id) return null;
    // Get the role id for this users group association
    const roleAssociation = await this.prisma.rbac_users_has_roles.findFirst({
      where: {
        users_has_group_id
      },
      select: {
        roles_id: true
      }
    });
    if (!roleAssociation) return null;
    // Get the role
    const role = await this.prisma.rbac_roles.findUnique({
      where: {
        id: roleAssociation.roles_id
      },
      select: {
        name: true
      }
    });
    if (!role) return null;
    return { id: roleAssociation.roles_id, name: role.name };
  }

  /*
   * @description Assign a set of features to a member
   */
  async addRoleToGroupMember(roles_id: number, user_group_id: number, user_id: number): Promise<void> {
    const users_has_group_id = await this.usersService.findUserHasGroupId(user_id, user_group_id);
    if (!users_has_group_id) return;
    await this.prisma.rbac_users_has_roles.create({
      data: {
        users_has_group_id,
        roles_id
      }
    });
    return;
  }

  /*
   * @description Update the features that are available to a member
   */
  async updateRoleForGroupMember(roles_id: number, user_group_id: number, user_id: number): Promise<void> {
    const users_has_group_id = await this.usersService.findUserHasGroupId(user_id, user_group_id);
    if (!users_has_group_id) return;
    // Get the users role assocation for this group
    const rbac_users_has_roles = await this.prisma.rbac_users_has_roles.findFirst({
      where: {
        users_has_group_id
      },
      select: {
        id: true,
        roles_id: true
      }
    });
    // If there is no role assigned to the user (new user)
    if (!rbac_users_has_roles) {
      await this.prisma.rbac_users_has_roles.create({
        data: {
          users_has_group_id,
          roles_id
        }
      });
    }
    // If there is an existing role where the role differs
    else if (rbac_users_has_roles.roles_id !== roles_id) {
      await this.prisma.rbac_users_has_roles.update({
        where: {
          id: rbac_users_has_roles.id
        },
        data: {
          roles_id
        }
      });
    }
    return;
  }

  /*
   * @description Remove a set of features from a member
   */
  async removeRoleFromGroupMember(roles_id: number, user_group_id: number, user_id: number): Promise<void> {
    const users_has_group_id = await this.usersService.findUserHasGroupId(user_id, user_group_id);
    if (!users_has_group_id) return;
    await this.prisma.rbac_users_has_roles.delete({
      where: {
        users_has_group_id_roles_id: {
          users_has_group_id,
          roles_id
        }
      }
    });
    return;
  }

  // async isGroupManager(user_group_id: number, user_id: number): Promise<RoleName[]> {
  //   const usersRoles = await this.findIdsByUserId(user_id);
  //   const result = this.prisma.user_group.create({
  //     data
  //   });
  //   if (!result) throw new ForbiddenException();
  //   return result;
  // }


  // async isUsersManager(user_id: number, added_by_user_id: number): Promise<RoleName[]> {

  //   const result = this.prisma.user_id.create({
  //     data
  //   });
  //   if (!result) throw new ForbiddenException();
  //   return result;
  // }

  // async addUser(role_id: number, user_id: number): Promise<RoleName[]> {
  //   return this.prisma..create({
  //     data
  //   });
  // }

  // async removeUser(role_id: number, user_id: number): Promise<RoleName[]> {
  //   return this.prisma..create({
  //     data
  //   });
  // }
}
