"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RoleService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const event_emitter_1 = require("@nestjs/event-emitter");
const auth_config_service_1 = require("../../core/services/auth-config.service");
const auth_constants_1 = require("../../auth.constants");
const permission_entity_1 = require("../../permission/entities/permission.entity");
const utils_1 = require("../../utils");
const role_entity_1 = require("../entities/role.entity");
const role_permission_entity_1 = require("../entities/role-permission.entity");
const role_mapper_util_1 = require("../utils/role-mapper.util");
const role_created_event_1 = require("../events/role-created.event");
const role_updated_event_1 = require("../events/role-updated.event");
const role_deleted_event_1 = require("../events/role-deleted.event");
let RoleService = class RoleService {
    roleRepository;
    dataSource;
    authConfigService;
    eventEmitter;
    constructor(roleRepository, dataSource, authConfigService, eventEmitter) {
        this.roleRepository = roleRepository;
        this.dataSource = dataSource;
        this.authConfigService = authConfigService;
        this.eventEmitter = eventEmitter;
    }
    getDefaultRoleGuard() {
        return this.authConfigService.getRoleGuards()[0] ?? auth_constants_1.DEFAULT_GUARD_NAME;
    }
    getRoleRelations(includeTenant = true) {
        const relations = ['rolePermissions', 'rolePermissions.permission'];
        if (includeTenant) {
            relations.push('tenant');
        }
        return relations;
    }
    resolveAndValidateGuard(guard) {
        const resolved = guard ?? this.getDefaultRoleGuard();
        if (!this.authConfigService.isRoleGuardAllowed(resolved)) {
            throw new common_1.BadRequestException({
                message: `Guard '${resolved}' is not allowed. Allowed guards: ${this.authConfigService.getRoleGuards().join(', ')}`,
                code: auth_constants_1.GUARD_ERROR_CODES.GUARD_NOT_ALLOWED,
            });
        }
        return resolved;
    }
    normalizePermissionNames(permissionNames) {
        if (permissionNames === undefined || permissionNames === null) {
            return [];
        }
        const values = Array.isArray(permissionNames) ? permissionNames : [permissionNames];
        const normalized = [];
        const seen = new Set();
        for (const value of values) {
            const name = value?.trim();
            if (!name || seen.has(name)) {
                continue;
            }
            seen.add(name);
            normalized.push(name);
        }
        return normalized;
    }
    buildRoleConflictMessage(name, guard, tenantId) {
        const scope = tenantId ? `tenant '${tenantId}'` : 'global scope';
        return `Role with name '${name}' already exists in guard '${guard}' for ${scope}`;
    }
    async ensureRoleIsUnique(manager, params) {
        const existingRole = await manager
            .getRepository(role_entity_1.NestAuthRole)
            .createQueryBuilder('role')
            .where('role.name = :name', { name: params.name })
            .andWhere('role.guard = :guard', { guard: params.guard })
            .andWhere(new typeorm_2.Brackets((qb) => {
            qb.where('role.tenantId IS NULL');
            if (params.tenantId) {
                qb.orWhere('role.tenantId = :tenantId', { tenantId: params.tenantId });
            }
        }))
            .getOne();
        if (existingRole && existingRole.id !== params.id) {
            throw new common_1.ConflictException({
                message: this.buildRoleConflictMessage(params.name, params.guard, params.tenantId),
                code: 'ROLE_ALREADY_EXISTS',
            });
        }
    }
    async resolvePermissionsByNames(manager, permissionNames, roleGuard) {
        const normalizedNames = this.normalizePermissionNames(permissionNames);
        if (!normalizedNames.length) {
            return [];
        }
        const permissionRepo = manager.getRepository(permission_entity_1.NestAuthPermission);
        const matchingPermissions = await permissionRepo
            .createQueryBuilder('permission')
            .where('permission.guard = :guard', { guard: roleGuard })
            .andWhere('permission.name IN (:...names)', { names: normalizedNames })
            .getMany();
        const byName = new Map(matchingPermissions.map((p) => [p.name, p]));
        const missingNames = normalizedNames.filter((name) => !byName.has(name));
        if (missingNames.length) {
            const list = missingNames.join(', ');
            throw new common_1.BadRequestException({
                message: missingNames.length === 1
                    ? `Permission '${missingNames[0]}' was not found for guard '${roleGuard}'.`
                    : `Permissions were not found for guard '${roleGuard}': ${list}.`,
                code: 'ROLE_PERMISSION_VALIDATION_FAILED',
                guard: roleGuard,
                missingPermissions: missingNames,
            });
        }
        return normalizedNames.map((name) => byName.get(name));
    }
    async replaceRolePermissions(manager, roleId, permissions) {
        const rolePermissionRepo = manager.getRepository(role_permission_entity_1.NestAuthRolePermission);
        await rolePermissionRepo.delete({ roleId });
        if (!permissions.length) {
            return;
        }
        const rolePermissions = permissions.map((permission) => rolePermissionRepo.create({
            roleId,
            permissionId: permission.id,
        }));
        await rolePermissionRepo.save(rolePermissions);
    }
    async getHydratedRole(manager, id, includeTenant = true) {
        return manager.getRepository(role_entity_1.NestAuthRole).findOne({
            where: { id },
            relations: this.getRoleRelations(includeTenant),
        });
    }
    async createRole(name, guard, tenantId = null, isSystem = false, permissionNames, isActive = true) {
        const normalizedName = name?.trim();
        if (!normalizedName) {
            throw new common_1.BadRequestException({
                message: 'Role name is required',
                code: 'ROLE_NAME_REQUIRED',
            });
        }
        const resolvedGuard = this.resolveAndValidateGuard(guard);
        const normalizedTenantId = tenantId?.trim() || null;
        const roleTenantId = isSystem ? null : normalizedTenantId;
        const createdRole = await this.dataSource.transaction(async (manager) => {
            await this.ensureRoleIsUnique(manager, {
                name: normalizedName,
                guard: resolvedGuard,
                tenantId: roleTenantId,
            });
            const resolvedPermissions = await this.resolvePermissionsByNames(manager, permissionNames, resolvedGuard);
            const role = manager.getRepository(role_entity_1.NestAuthRole).create({
                name: normalizedName,
                guard: resolvedGuard,
                tenantId: roleTenantId,
                isSystem,
                isActive,
            });
            let savedRole;
            try {
                savedRole = await manager.getRepository(role_entity_1.NestAuthRole).save(role);
            }
            catch (error) {
                if ((0, utils_1.isUniqueConstraintViolation)(error)) {
                    throw new common_1.ConflictException({
                        message: this.buildRoleConflictMessage(normalizedName, resolvedGuard, roleTenantId),
                        code: 'ROLE_ALREADY_EXISTS',
                    });
                }
                throw error;
            }
            await this.replaceRolePermissions(manager, savedRole.id, resolvedPermissions);
            return this.getHydratedRole(manager, savedRole.id, true);
        });
        await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.ROLE_CREATED, new role_created_event_1.RoleCreatedEvent({ role: createdRole }));
        return createdRole;
    }
    async getRoleById(id, options) {
        if (!id) {
            return null;
        }
        return this.roleRepository.findOne({
            ...(options ? options : {}),
            where: { id },
            relations: Array.isArray(options?.relations)
                ? Array.from(new Set([...this.getRoleRelations(true), ...options.relations]))
                : this.getRoleRelations(true),
        });
    }
    async getRoleByName(name, guard, tenantId, options) {
        const relations = Array.isArray(options?.relations)
            ? Array.from(new Set([...this.getRoleRelations(true), ...options.relations]))
            : this.getRoleRelations(true);
        const systemRole = await this.roleRepository.findOne({
            ...(options ? options : {}),
            where: {
                name,
                ...(guard ? { guard } : {}),
                isSystem: true,
            },
            relations,
        });
        if (systemRole) {
            return systemRole;
        }
        return this.roleRepository.findOne({
            ...(options ? options : {}),
            where: {
                name,
                ...(guard ? { guard } : {}),
                ...(tenantId ? { tenantId } : { tenantId: (0, typeorm_2.IsNull)() }),
            },
            relations,
        });
    }
    async getSystemRoles(options) {
        return this.roleRepository.find({
            ...(options ? options : {}),
            where: {
                isSystem: true,
                tenantId: (0, typeorm_2.IsNull)(),
            },
            relations: Array.isArray(options?.relations)
                ? Array.from(new Set([...this.getRoleRelations(true), ...options.relations]))
                : this.getRoleRelations(true),
            order: {
                name: 'ASC',
            },
        });
    }
    async getRoles(params = {}, options) {
        const { guard, onlyTenantRoles, onlySystemRoles, includeTenant } = params;
        const { tenantId } = params;
        const query = this.roleRepository.createQueryBuilder('role');
        query
            .leftJoinAndSelect('role.rolePermissions', 'rolePermission')
            .leftJoinAndSelect('rolePermission.permission', 'permission')
            .distinct(true);
        if (guard) {
            query.andWhere('role.guard = :guard', { guard });
        }
        if (onlySystemRoles) {
            query.andWhere('role.isSystem = :isSystem', { isSystem: true });
        }
        else if (onlyTenantRoles) {
            if (!tenantId) {
                return [];
            }
            query.andWhere('role.tenantId = :tenantId', { tenantId });
        }
        else if (tenantId) {
            query.andWhere(new typeorm_2.Brackets((qb) => {
                qb.where('role.tenantId = :tenantId', { tenantId })
                    .orWhere('role.isSystem = :isSystem', { isSystem: true });
            }));
        }
        if (includeTenant) {
            query.leftJoinAndSelect('role.tenant', 'tenant');
        }
        if (options?.where) {
            query.andWhere(options.where);
        }
        if (options?.order) {
            Object.entries(options.order).forEach(([key, value]) => {
                query.addOrderBy(`role.${key}`, value);
            });
        }
        else {
            query.orderBy('role.name', 'ASC');
        }
        return query.getMany();
    }
    async updateRole(id, data) {
        const updatedRole = await this.dataSource.transaction(async (manager) => {
            const role = await this.getHydratedRole(manager, id, true);
            if (!role) {
                throw new common_1.NotFoundException({
                    message: `Role with ID ${id} not found`,
                    code: 'ROLE_NOT_FOUND',
                });
            }
            const nextName = data.name !== undefined ? data.name.trim() : role.name;
            if (!nextName) {
                throw new common_1.BadRequestException({
                    message: 'Role name is required',
                    code: 'ROLE_NAME_REQUIRED',
                });
            }
            const shouldReplacePermissions = Object.prototype.hasOwnProperty.call(data, 'permissions');
            const nextPermissionNames = shouldReplacePermissions
                ? data.permissions
                : (0, role_mapper_util_1.getRolePermissionNames)(role);
            await this.ensureRoleIsUnique(manager, {
                id,
                name: nextName,
                guard: role.guard,
                tenantId: role.tenantId,
            });
            const resolvedPermissions = shouldReplacePermissions
                ? await this.resolvePermissionsByNames(manager, nextPermissionNames, role.guard)
                : [];
            role.name = nextName;
            if (data.isActive !== undefined) {
                role.isActive = data.isActive;
            }
            try {
                await manager.getRepository(role_entity_1.NestAuthRole).save(role);
            }
            catch (error) {
                if ((0, utils_1.isUniqueConstraintViolation)(error)) {
                    throw new common_1.ConflictException({
                        message: this.buildRoleConflictMessage(nextName, role.guard, role.tenantId),
                        code: 'ROLE_ALREADY_EXISTS',
                    });
                }
                throw error;
            }
            if (shouldReplacePermissions) {
                await this.replaceRolePermissions(manager, role.id, resolvedPermissions);
            }
            return this.getHydratedRole(manager, role.id, true);
        });
        await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.ROLE_UPDATED, new role_updated_event_1.RoleUpdatedEvent({ role: updatedRole, updatedFields: Object.keys(data ?? {}) }));
        return updatedRole;
    }
    async updateRolePermissions(id, permissionNames) {
        return this.updateRole(id, { permissions: permissionNames });
    }
    async deleteRole(id) {
        const role = await this.getRoleById(id);
        if (!role) {
            throw new common_1.NotFoundException({
                message: `Role with ID ${id} not found`,
                code: 'ROLE_NOT_FOUND',
            });
        }
        await this.roleRepository.remove(role);
        await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.ROLE_DELETED, new role_deleted_event_1.RoleDeletedEvent({ role }));
    }
};
exports.RoleService = RoleService;
exports.RoleService = RoleService = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(role_entity_1.NestAuthRole)),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.DataSource,
        auth_config_service_1.AuthConfigService,
        event_emitter_1.EventEmitter2])
], RoleService);
//# sourceMappingURL=role.service.js.map