import { AppRole, Role } from '@/entities/role.entity';
import CoreService from '@/system/core/core-service';
import { SystemLang } from '@/system/lang/system.lang';
import AppResponse from '@/system/response/ktq-response';
import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Request } from 'express';
import * as _ from 'lodash';
import { In, Repository } from 'typeorm';
import { CreateRoleDto } from './dtos/create-role.dto';
import { ExtendsDto } from './dtos/extends.dto';

@Injectable()
export class RolesService extends CoreService<Role> {
  constructor(
    @InjectRepository(Role)
    readonly repo: Repository<Role>,
  ) {
    super(
      repo,
      {
        sortableColumns: ['id'],
        defaultSortBy: [['id', 'DESC']],
        filterableColumns: {
          role_key: true,
          id: true,
        },
        relations: {
          permissions: true,
          resources: true,
          role_authors: {
            role: true,
          },
        },
      },
      Role,
    );
  }

  protected async beforeDelete(
    id: number,
    data: Partial<Role>,
    req: Request,
  ): Promise<boolean> {
    if (data.role_key === AppRole.ROOT) {
      throw new BadRequestException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'can_not_delete', 'Role'),
        }),
      );
    }

    return true;
  }

  protected async afterDelete(
    id: number,
    data: Partial<Role>,
    req: Request,
  ): Promise<void> {
    if (!data.role_key) return;

    await this.repo.manager.transaction(async (manager) => {
      const inheritedRoles = await manager
        .createQueryBuilder(Role, 'role')
        .where(':roleKey = ANY(role.inherits)', { roleKey: data.role_key })
        .getMany();

      for (const role of inheritedRoles) {
        role.inherits = role.inherits.filter((key) => key !== data.role_key);
        await manager.save(role);
      }
    });
  }

  async create(data: CreateRoleDto) {
    const result = await this.repo.save({ ...data });

    if (!result)
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getText('messages', 'create_failed'),
        }),
      );

    return AppResponse.toResponse(result, {
      message: SystemLang.getText('messages', 'create_success'),
    });
  }

  async extends(id: Role['id'], values: ExtendsDto) {
    if (id != values.id) {
      throw new BadRequestException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'error'),
        }),
      );
    }

    const entity = await this.repo.findOne({ where: { id } });

    if (!entity) {
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'not_found'),
        }),
      );
    }

    if (entity.role_key === AppRole.ROOT) {
      throw new BadRequestException(
        AppResponse.toResponse(false, {
          message: SystemLang.getCustomText({
            en: 'This operation cannot be applied to ROOT Role',
            vi: 'Không thể áp dụng thao tác này cho ROOT Role',
          }),
        }),
      );
    }

    const inherits = _.uniq([...values.inherits]);

    const result = await this.repo.update(id, { inherits });

    if (!result) {
      throw new BadRequestException(
        AppResponse.toResponse(false, {
          message: SystemLang.getText('messages', 'error'),
        }),
      );
    }

    return AppResponse.toResponse(true);
  }

  async inherit(parent_role: Role) {
    const children = await this.repo.find({
      where: { id: In(parent_role.inherits) },
    });

    return children;
  }

  async inherits(parent_roles: Role[]): Promise<Role[]> {
    // Lấy role keys trong parent roles
    const roleKeys = _.flatMap(parent_roles, (r) => r.inherits);

    // Nếu role keys bằng [] thì dừng lại
    if (roleKeys.length === 0) return [];

    // Tìm roles trong DB bằng số role keys vừa tìm
    const roles = await this.repo.find({
      where: { role_key: In(roleKeys) },
      relations: { permissions: true, resources: true },
    });

    // Đệ quy lấy hết role ra
    const inherited = await this.inherits(roles);

    // Merge role children lại
    return _.unionBy([...roles, ...inherited], 'id');
  }
}
