"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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccessControlService = void 0;
const crypto_1 = require("crypto");
const common_1 = require("@nestjs/common");
const config_1 = require("../../config");
const entities_1 = require("../../config/database/entities");
const permission_resolver_service_1 = require("../../common/access-control/permission-resolver.service");
const utils_1 = require("../../utils");
const access_control_dto_1 = require("./access-control.dto");
const permission_catalog_1 = require("./permission-catalog");
let AccessControlService = class AccessControlService {
    constructor(db, permissionResolver) {
        this.db = db;
        this.permissionResolver = permissionResolver;
    }
    getPermissionCatalog() {
        return {
            data: permission_catalog_1.PERMISSION_MODULES,
            message: 'Permission catalog retrieved successfully',
            status: common_1.HttpStatus.OK,
            details: {
                total_modules: permission_catalog_1.PERMISSION_MODULES.length,
                total_permissions: permission_catalog_1.ALL_PERMISSION_KEYS.length,
            },
        };
    }
    slugify(name) {
        return name
            .trim()
            .toLowerCase()
            .replace(/[^a-z0-9]+/g, '_')
            .replace(/^_+|_+$/g, '')
            .slice(0, 80);
    }
    async getRolePermissionsMap(roleId) {
        const rows = await this.db.role_permissions.findMany({
            where: { role_id: roleId },
        });
        return rows.reduce((acc, row) => {
            acc[row.permission_key] = row.permission_value;
            return acc;
        }, {});
    }
    mergePermissionMap(slug, overrides = {}) {
        const defaults = (0, permission_catalog_1.getDefaultPermissionMap)(slug);
        const merged = { ...defaults };
        for (const [key, value] of Object.entries(overrides)) {
            if (permission_catalog_1.ALL_PERMISSION_KEYS.includes(key)) {
                merged[key] = value;
            }
        }
        return merged;
    }
    formatRoleResponse(role, permissionMap) {
        const { data } = access_control_dto_1.accessRoleResponseSchema.safeParse({
            ...role,
            permissions: permissionMap,
        });
        return data ?? {
            ...role,
            permissions: permissionMap,
        };
    }
    isDeadlockError(error) {
        const err = error;
        const message = String(err?.message ?? error);
        return (message.includes('Deadlock') ||
            err?.errno === 1213 ||
            err?.code === 'ER_LOCK_DEADLOCK');
    }
    buildPermissionRows(roleId, permissionMap) {
        return Object.entries(permissionMap)
            .filter(([permission_key]) => permission_catalog_1.ALL_PERMISSION_KEYS.includes(permission_key))
            .map(([permission_key, permission_value]) => ({
            id: (0, crypto_1.randomUUID)(),
            role_id: roleId,
            permission_key,
            permission_value,
        }));
    }
    async persistRolePermissions(roleId, permissionMap) {
        const rows = this.buildPermissionRows(roleId, permissionMap);
        const maxAttempts = 3;
        for (let attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                await this.db.runInTransaction(async (manager) => {
                    await manager.delete(entities_1.RolePermissionEntity, { role_id: roleId });
                    if (rows.length > 0) {
                        await manager.insert(entities_1.RolePermissionEntity, rows);
                    }
                });
                return;
            }
            catch (error) {
                if (!this.isDeadlockError(error) || attempt === maxAttempts) {
                    throw error;
                }
                await new Promise((resolve) => setTimeout(resolve, 50 * attempt));
            }
        }
    }
    async ensureOwnerPermissionKeysExist(owner_id) {
        const ownerRole = await this.db.access_roles.findFirst({
            where: { owner_id, slug: 'owner' },
        });
        if (!ownerRole) {
            return;
        }
        const defaults = (0, permission_catalog_1.getDefaultPermissionMap)('owner');
        const existing = await this.getRolePermissionsMap(ownerRole.id);
        const missing = Object.entries(defaults).filter(([permission_key]) => permission_catalog_1.ALL_PERMISSION_KEYS.includes(permission_key) &&
            existing[permission_key] === undefined);
        if (missing.length === 0) {
            return;
        }
        await this.db.role_permissions.createMany({
            data: missing.map(([permission_key, permission_value]) => ({
                id: (0, crypto_1.randomUUID)(),
                role_id: ownerRole.id,
                permission_key,
                permission_value,
            })),
        });
    }
    async ensureDefaultRoles(owner_id) {
        const existing = await this.db.access_roles.findMany({
            where: { owner_id },
            orderBy: { hierarchy: 'desc' },
        });
        if (existing.length > 0) {
            await this.ensureOwnerPermissionKeysExist(owner_id);
            return existing;
        }
        for (const template of permission_catalog_1.SYSTEM_ROLE_TEMPLATES) {
            const role = await this.db.access_roles.create({
                data: {
                    owner_id,
                    name: template.name,
                    slug: template.slug,
                    description: template.description,
                    is_system: template.is_system,
                    hierarchy: template.hierarchy,
                },
            });
            await this.persistRolePermissions(role.id, (0, permission_catalog_1.getDefaultPermissionMap)(template.slug));
        }
        return this.db.access_roles.findMany({
            where: { owner_id },
            orderBy: { hierarchy: 'desc' },
        });
    }
    async getRoles(owner_id) {
        try {
            const roles = await this.ensureDefaultRoles(owner_id);
            const formatted = await Promise.all(roles.map(async (role) => {
                const permissions = await this.getRolePermissionsMap(role.id);
                return this.formatRoleResponse(role, this.mergePermissionMap(role.slug, permissions));
            }));
            const { data } = access_control_dto_1.accessRolesResponseSchema.safeParse(formatted);
            return {
                data: data ?? formatted,
                message: 'Access roles retrieved successfully',
                status: common_1.HttpStatus.OK,
                details: { total_roles: formatted.length },
            };
        }
        catch (error) {
            config_1.logger.error('Failed to fetch access roles', error);
            throw error;
        }
    }
    async getRoleById(roleId, owner_id) {
        const role = await this.db.access_roles.findFirst({
            where: { id: roleId, owner_id },
        });
        if (!role) {
            throw (0, utils_1.createGeneralError)('Role not found', 'role_id', common_1.HttpStatus.NOT_FOUND);
        }
        const permissions = await this.getRolePermissionsMap(role.id);
        return {
            data: this.formatRoleResponse(role, this.mergePermissionMap(role.slug, permissions)),
            message: 'Access role retrieved successfully',
            status: common_1.HttpStatus.OK,
            details: null,
        };
    }
    async createRole(data, owner_id) {
        try {
            const baseSlug = this.slugify(data.name);
            let slug = baseSlug || `role_${Date.now()}`;
            let suffix = 1;
            while (await this.db.access_roles.findFirst({
                where: { owner_id, slug },
            })) {
                slug = `${baseSlug}_${suffix}`;
                suffix += 1;
            }
            let permissionMap = (0, permission_catalog_1.getDefaultPermissionMap)('member');
            if (data.source_role_id) {
                const sourceRole = await this.db.access_roles.findFirst({
                    where: { id: data.source_role_id, owner_id },
                });
                if (!sourceRole) {
                    throw (0, utils_1.createGeneralError)('Source role not found', 'source_role_id', common_1.HttpStatus.NOT_FOUND);
                }
                const sourcePermissions = await this.getRolePermissionsMap(sourceRole.id);
                permissionMap = this.mergePermissionMap(sourceRole.slug, sourcePermissions);
            }
            const role = await this.db.access_roles.create({
                data: {
                    owner_id,
                    name: data.name.trim(),
                    slug,
                    description: data.description ?? 'NT/APL',
                    is_system: false,
                    hierarchy: 10,
                },
            });
            await this.persistRolePermissions(role.id, permissionMap);
            return {
                data: this.formatRoleResponse(role, permissionMap),
                message: 'Access role created successfully',
                status: common_1.HttpStatus.CREATED,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error('Failed to create access role', error);
            throw error;
        }
    }
    async updateRole(roleId, data, owner_id) {
        try {
            const role = await this.db.access_roles.findFirst({
                where: { id: roleId, owner_id },
            });
            if (!role) {
                throw (0, utils_1.createGeneralError)('Role not found', 'role_id', common_1.HttpStatus.NOT_FOUND);
            }
            if (role.slug === 'owner') {
                throw (0, utils_1.createGeneralError)('Owner role permissions cannot be modified', 'role_id', common_1.HttpStatus.FORBIDDEN);
            }
            const updatePayload = {};
            if (data.description !== undefined) {
                updatePayload.description = data.description ?? 'NT/APL';
            }
            if (data.name !== undefined && !role.is_system) {
                updatePayload.name = data.name.trim();
            }
            if (Object.keys(updatePayload).length > 0) {
                await this.db.access_roles.update({
                    where: { id: roleId },
                    data: updatePayload,
                });
            }
            if (data.permissions) {
                const currentPermissions = await this.getRolePermissionsMap(role.id);
                const merged = this.mergePermissionMap(role.slug, {
                    ...currentPermissions,
                    ...data.permissions,
                });
                await this.persistRolePermissions(role.id, merged);
            }
            const updatedRole = await this.db.access_roles.findFirst({
                where: { id: roleId, owner_id },
            });
            const permissions = await this.getRolePermissionsMap(roleId);
            return {
                data: this.formatRoleResponse(updatedRole, this.mergePermissionMap(updatedRole.slug, permissions)),
                message: 'Access role updated successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error('Failed to update access role', error);
            throw error;
        }
    }
    async deleteRole(roleId, owner_id) {
        try {
            const role = await this.db.access_roles.findFirst({
                where: { id: roleId, owner_id },
            });
            if (!role) {
                throw (0, utils_1.createGeneralError)('Role not found', 'role_id', common_1.HttpStatus.NOT_FOUND);
            }
            if (role.is_system) {
                throw (0, utils_1.createGeneralError)('System roles cannot be deleted', 'role_id', common_1.HttpStatus.FORBIDDEN);
            }
            await this.db.access_roles.delete({
                where: { id: roleId },
            });
            return {
                data: null,
                message: 'Access role deleted successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error('Failed to delete access role', error);
            throw error;
        }
    }
    async resolveAccessRoleIdBySlug(owner_id, slug) {
        await this.ensureDefaultRoles(owner_id);
        const role = await this.db.access_roles.findFirst({
            where: { owner_id, slug },
            select: { id: true },
        });
        return role?.id ?? null;
    }
    async getMyPermissions(ctx) {
        await this.ensureDefaultRoles(ctx.owner_id);
        const permissions = await this.permissionResolver.resolveForUser(ctx);
        const accessRoleId = await this.permissionResolver.resolveAccessRoleId(ctx);
        const accessRole = accessRoleId
            ? await this.db.access_roles.findFirst({
                where: { id: accessRoleId, owner_id: ctx.owner_id },
                select: { slug: true },
            })
            : null;
        return {
            data: {
                permissions,
                access_role_id: accessRoleId,
                role_slug: accessRole?.slug ?? ctx.role,
            },
            message: 'Permissions retrieved successfully',
            status: common_1.HttpStatus.OK,
            details: null,
        };
    }
};
exports.AccessControlService = AccessControlService;
exports.AccessControlService = AccessControlService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [config_1.DatabaseService,
        permission_resolver_service_1.PermissionResolverService])
], AccessControlService);
//# sourceMappingURL=access-control.service.js.map