import Auth from '@/entities/core/auth';
import { Permission } from '@/entities/permission.entity';
import { Resource } from '@/entities/resource.entity';
import { RoleAuthor } from '@/entities/role-author.entity';
import { AppRole, Role } from '@/entities/role.entity';
import { JwtService } from '@nestjs/jwt';
import * as _ from 'lodash';
import { In, Repository } from 'typeorm';
import { ResourceService } from '../../resources/resource.service';
import { RolesService } from '@/modules/roles/roles.service';

export class AuthorService<T extends Auth> {
  private author_table_name = '';

  constructor(
    protected jwtService: JwtService,
    protected readonly roleService: RolesService,
    protected readonly resourceService: ResourceService,
    protected readonly authRepo: Repository<T>,
    protected readonly roleAuthorRepo: Repository<RoleAuthor>,
    protected readonly permissionRepo: Repository<Permission>,
    protected readonly classRef: new (...args: any[]) => T,
  ) {
    this.author_table_name = this.classRef['table_name'];
  }

  async roles(user: T): Promise<Role[]> {
    const roles = await this.roleAuthorRepo.find({
      where: {
        author_table: this.author_table_name,
        author_id: String(user.id),
      },
      relations: { role: { permissions: true, resources: true } },
    });

    const children = await this.roleService.inherits(_.map(roles, 'role'));

    return _.unionBy([..._.map(roles, 'role'), ...children], 'id');
  }

  async isRootRole(roles: Role[] | Set<Role>) {
    const roleArray = Array.isArray(roles) ? roles : Array.from(roles);
    return roleArray.some((role) => role.role_key === AppRole.ROOT);
  }

  async currentResource(query: { path: string; method: string }) {
    return await this.resourceService.repo.findOne({ where: { ...query } });
  }

  async getPermissionsForResource(user: T, resource: Resource) {
    const data = await this.resourceService.resourcePermissionRepo.find({
      where: {
        author_id: String(user.id),
        author_table: this.author_table_name,
        resource_id: resource.id,
      },
      relations: {
        permission: true,
      },
    });

    return _.map(data, 'permission');
  }

  async rolePermissions(roles: Role[] | Set<Role>) {
    return _(roles)
      .thru((r) => (Array.isArray(r) ? r : Array.from(r)))
      .flatMap('permissions')
      .uniqBy('id')
      .value();
  }

  async isValidResource(resrouce: Resource, roles: Role[]) {
    const roleIds = _.map(roles, 'id');

    const hasAccess = await this.resourceService.repo.findOne({
      where: { roles: { id: In(roleIds) }, id: resrouce.id },
    });

    return !!hasAccess;
  }
}
