"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.UserService = void 0;
const api_locale_1 = require("@hed-hog/api-locale");
const api_pagination_1 = require("@hed-hog/api-pagination");
const api_prisma_1 = require("@hed-hog/api-prisma");
const common_1 = require("@nestjs/common");
const challenge_service_1 = require("../challenge/challenge.service");
const file_service_1 = require("../file/file.service");
const integration_developer_api_service_1 = require("../integration/services/integration-developer-api.service");
const security_service_1 = require("../security/security.service");
const user_constants_1 = require("./constants/user.constants");
// Constants
const IDENTIFIER_TYPE = {
    EMAIL: 'email',
};
const CREDENTIAL_TYPE = {
    PASSWORD: 'password',
};
const DEFAULT_ROLE_SLUG = 'user';
const DEFAULT_LOCALE = 'en';
const DAYS_IN_MS = 24 * 60 * 60 * 1000;
const NEW_USERS_PERIOD_DAYS = 7;
const RANDOM_PASSWORD_LENGTH = 16;
const PASSWORD_CHARSETS = {
    lowercase: 'abcdefghijkmnopqrstuvwxyz',
    uppercase: 'ABCDEFGHJKLMNPQRSTUVWXYZ',
    numbers: '23456789',
    symbols: '@#$%&*!?-_+',
};
const USER_SORT_FIELDS = [
    'id',
    'name',
    'photo_id',
    'suspended_until',
    'suspended_reason',
    'deactivated_at',
    'created_at',
    'updated_at',
];
const USER_SEARCH_FIELDS = ['name'];
let UserService = class UserService {
    constructor(prismaService, paginationService, security, challenge, file, integrationApi) {
        this.prismaService = prismaService;
        this.paginationService = paginationService;
        this.security = security;
        this.challenge = challenge;
        this.file = file;
        this.integrationApi = integrationApi;
    }
    // ==========================================
    // Validation Helpers
    // ==========================================
    async validateUserExists(locale, userId) {
        const user = await this.prismaService.user.findUnique({
            where: { id: userId },
        });
        if (!user) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found'));
        }
        return user;
    }
    async validateRoleExists(locale, roleId) {
        const role = await this.prismaService.role.findUnique({
            where: { id: roleId },
        });
        if (!role) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('roleNotFound', locale, 'Role not found'));
        }
        return role;
    }
    async validateEmailAvailable(locale, email) {
        const existingUser = await this.findUserByEmail(locale, email);
        if (existingUser) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('signup.emailExists', locale, 'Email already in use.'));
        }
    }
    // ==========================================
    // User Identifier Methods
    // ==========================================
    async verifyIdentifier(locale, userId, identifierId) {
        const identifier = await this.prismaService.user_identifier.findFirst({
            where: {
                id: identifierId,
                user_id: userId,
            },
        });
        if (!identifier) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userIdentifierNotFound', locale, 'Identifier not found for this user'));
        }
        return identifier;
    }
    // ==========================================
    // CRUD Operations
    // ==========================================
    async list(paginationParams) {
        const paginate = await this.paginationService.paginatePrismaModel(this.prismaService.user, Object.assign(Object.assign({}, paginationParams), { validSortFields: USER_SORT_FIELDS, searchFields: USER_SEARCH_FIELDS, include: this.getUserIncludeClause() }));
        const stats = await this.getUserStatistics();
        return { paginate, stats };
    }
    async get(locale, userId) {
        return this.findUserById(locale, userId);
    }
    async createWithEmailAndPassword(locale, { email, name, password }) {
        await this.validateEmailAvailable(locale, email);
        const passwordHash = await this.security.hashArgon2(password);
        const user = await this.prismaService.$transaction(async (tx) => {
            const role = await this.getDefaultUserRole(locale, tx);
            const user = await tx.user.create({
                data: {
                    name,
                    user_identifier: {
                        create: {
                            type: IDENTIFIER_TYPE.EMAIL,
                            value: email,
                            enabled: true,
                        },
                    },
                    user_credential: {
                        create: {
                            type: CREDENTIAL_TYPE.PASSWORD,
                            hash: passwordHash,
                        },
                    },
                    role_user: {
                        create: {
                            role_id: role.id,
                        },
                    },
                },
            });
            return user;
        });
        const challenge = await this.challenge.verifyEmail(locale, user.id, email);
        await this.registerUserActivity(user.id, 'register');
        await this.integrationApi.publishEvent({
            eventName: 'core.user.created',
            sourceModule: 'core',
            aggregateType: 'user',
            aggregateId: String(user.id),
            payload: { id: user.id, name: user.name, email },
        }).catch(() => null);
        return { user, challenge };
    }
    async update(locale, userId, { name }) {
        const user = await this.findUserById(locale, userId);
        const result = await this.prismaService.user.update({
            where: { id: user.id },
            data: { name: name !== null && name !== void 0 ? name : user.name },
        });
        await this.integrationApi.publishEvent({
            eventName: 'core.user.updated',
            sourceModule: 'core',
            aggregateType: 'user',
            aggregateId: String(userId),
            payload: { id: userId, name: result.name },
        }).catch(() => null);
        return result;
    }
    async resetPassword(locale, userId, { password }) {
        await this.validateUserExists(locale, userId);
        const nextPassword = password || this.generateRandomPassword();
        const passwordHash = await this.security.hashArgon2(nextPassword);
        const updateResult = await this.prismaService.user_credential.updateMany({
            where: {
                user_id: userId,
                type: CREDENTIAL_TYPE.PASSWORD,
            },
            data: {
                hash: passwordHash,
                requires_reset: true,
            },
        });
        if (updateResult.count === 0) {
            await this.prismaService.user_credential.create({
                data: {
                    user_id: userId,
                    type: CREDENTIAL_TYPE.PASSWORD,
                    hash: passwordHash,
                    requires_reset: true,
                },
            });
        }
        await this.registerUserActivity(userId, 'resetPassword');
        return {
            password: nextPassword,
            requiresReset: true,
        };
    }
    async delete(locale, { ids }) {
        this.validateDeleteIds(locale, ids);
        const usersExists = await this.prismaService.user.findMany({
            where: { id: { in: ids } },
            select: { id: true },
        });
        if (usersExists.length !== ids.length) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'One or more users not found'));
        }
        const result = await this.prismaService.user.deleteMany({
            where: { id: { in: ids } },
        });
        await this.integrationApi.publishEvent({
            eventName: 'core.user.deleted',
            sourceModule: 'core',
            aggregateType: 'user',
            aggregateId: ids.join(','),
            payload: { ids },
        }).catch(() => null);
        return result;
    }
    // ==========================================
    // Avatar Management
    // ==========================================
    async changeAvatar(locale, userId, avatar) {
        const user = await this.validateUserExists(locale, userId);
        const newFile = await this.file.upload(user_constants_1.USER_AVATAR_UPLOAD_DESTINATION, avatar);
        await this.deleteOldAvatar(locale, user.photo_id);
        await this.prismaService.user.update({
            where: { id: userId },
            data: { photo_id: newFile.id },
        });
        return {
            id: newFile.id,
            path: newFile.path,
        };
    }
    async openPublicAvatar(locale, fileId, res) {
        const userWithAvatar = await this.prismaService.user.findFirst({
            where: {
                photo_id: fileId,
            },
            select: {
                id: true,
            },
        });
        if (!userWithAvatar) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found'));
        }
        const { file, buffer } = await this.file.getBuffer(fileId);
        res.set({
            'Content-Type': file.file_mimetype.name,
            'Content-Length': buffer.length,
            'Cache-Control': 'public, max-age=3600',
        });
        res.send(buffer);
    }
    // ==========================================
    // User Lookup Methods
    // ==========================================
    async findUserById(locale, id) {
        const user = await this.prismaService.user.findUnique({
            where: { id },
            include: {
                user_identifier: true,
                user_credential: true,
                user_activity: true,
                user_session: true,
                user_account: true,
                role_user: {
                    include: {
                        role: true,
                    },
                },
            },
        });
        if (!user) {
            throw new common_1.NotFoundException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found'));
        }
        return user;
    }
    async findUserByEmail(_locale, email) {
        return this.prismaService.user.findFirst({
            where: {
                user_identifier: {
                    some: {
                        type: IDENTIFIER_TYPE.EMAIL,
                        value: email.toLowerCase().trim(),
                        enabled: true,
                    },
                },
            },
            include: {
                user_identifier: true,
                user_credential: true,
                user_session: true,
            },
        });
    }
    // ==========================================
    // Role Management
    // ==========================================
    async getUserRoles(userId) {
        const [allRoles, userRoles] = await Promise.all([
            this.getAllRolesWithLocale(),
            this.getUserAssignedRoles(userId),
        ]);
        return this.mapRolesWithAssignment(allRoles, userRoles);
    }
    async getUserMenus(locale, userId) {
        const roleUsers = await this.prismaService.role_user.findMany({
            where: { user_id: userId },
            select: { role_id: true },
        });
        const roleIds = roleUsers.map((ru) => ru.role_id);
        const menus = await this.prismaService.menu.findMany({
            where: {
                role_menu: {
                    some: { role_id: { in: roleIds } },
                },
            },
            include: {
                menu_locale: {
                    where: { locale: { code: locale } },
                    select: { name: true },
                },
                role_menu: {
                    where: { role_id: { in: roleIds } },
                    include: {
                        role: {
                            select: {
                                slug: true,
                                role_locale: {
                                    where: { locale: { code: locale } },
                                    select: { name: true },
                                },
                            },
                        },
                    },
                },
            },
            orderBy: { order: 'asc' },
        });
        return menus.map((menu) => {
            var _a, _b, _c, _d;
            return ({
                id: menu.id,
                menuId: (_a = menu.menu_id) !== null && _a !== void 0 ? _a : null,
                url: menu.url,
                icon: menu.icon,
                order: menu.order,
                name: ((_c = (_b = menu.menu_locale) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.name) || menu.url || '',
                roles: ((_d = menu.role_menu) !== null && _d !== void 0 ? _d : []).map((rm) => {
                    var _a, _b, _c, _d, _e, _f, _g, _h;
                    return ({
                        name: (_f = (_d = (_c = (_b = (_a = rm.role) === null || _a === void 0 ? void 0 : _a.role_locale) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : (_e = rm.role) === null || _e === void 0 ? void 0 : _e.slug) !== null && _f !== void 0 ? _f : '',
                        slug: (_h = (_g = rm.role) === null || _g === void 0 ? void 0 : _g.slug) !== null && _h !== void 0 ? _h : '',
                    });
                }),
            });
        });
    }
    async getUserRoutes(locale, userId) {
        const roleUsers = await this.prismaService.role_user.findMany({
            where: { user_id: userId },
            select: { role_id: true },
        });
        const roleIds = roleUsers.map((ru) => ru.role_id);
        const routes = await this.prismaService.route.findMany({
            where: {
                role_route: {
                    some: { role_id: { in: roleIds } },
                },
            },
            include: {
                role_route: {
                    where: { role_id: { in: roleIds } },
                    include: {
                        role: {
                            select: {
                                slug: true,
                                role_locale: {
                                    where: { locale: { code: locale } },
                                    select: { name: true },
                                },
                            },
                        },
                    },
                },
            },
            orderBy: [{ method: 'asc' }, { url: 'asc' }],
        });
        return routes.map((route) => {
            var _a;
            return ({
                id: route.id,
                url: route.url,
                method: route.method,
                roles: ((_a = route.role_route) !== null && _a !== void 0 ? _a : []).map((rr) => {
                    var _a, _b, _c, _d, _e, _f, _g, _h;
                    return ({
                        name: (_f = (_d = (_c = (_b = (_a = rr.role) === null || _a === void 0 ? void 0 : _a.role_locale) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : (_e = rr.role) === null || _e === void 0 ? void 0 : _e.slug) !== null && _f !== void 0 ? _f : '',
                        slug: (_h = (_g = rr.role) === null || _g === void 0 ? void 0 : _g.slug) !== null && _h !== void 0 ? _h : '',
                    });
                }),
            });
        });
    }
    async assignRoleToUser(locale, userId, roleId) {
        await Promise.all([
            this.validateUserExists(locale, userId),
            this.validateRoleExists(locale, roleId),
        ]);
        const existingRoleUser = await this.findRoleUser(userId, roleId);
        if (existingRoleUser) {
            return existingRoleUser;
        }
        return this.prismaService.role_user.create({
            data: {
                user_id: userId,
                role_id: roleId,
            },
        });
    }
    async removeRoleFromUser(locale, userId, roleId) {
        await Promise.all([
            this.validateUserExists(locale, userId),
            this.validateRoleExists(locale, roleId),
        ]);
        const roleUser = await this.findRoleUser(userId, roleId);
        if (!roleUser) {
            return { count: 0 };
        }
        await this.prismaService.role_user.delete({
            where: { id: roleUser.id },
        });
        return { count: 1 };
    }
    // ==========================================
    // Activity Tracking
    // ==========================================
    async registerUserActivity(userId, action) {
        if (!userId || !Number.isInteger(userId) || userId <= 0) {
            return null;
        }
        return this.prismaService.user_activity.create({
            data: {
                user_id: userId,
                action,
            },
        });
    }
    // ==========================================
    // Private Helper Methods
    // ==========================================
    validateDeleteIds(locale, ids) {
        if (!ids || ids.length === 0) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('userIdsRequired', locale, 'At least one User ID is required to delete users.'));
        }
    }
    async deleteOldAvatar(locale, photoId) {
        if (photoId > 0) {
            try {
                await this.file.delete(locale, { ids: [photoId] });
            }
            catch (error) {
                // Fallback: delete directly from database if file service fails
                await this.prismaService.file.delete({
                    where: { id: photoId },
                }).catch(() => {
                    console.warn('Failed to delete old avatar:', error);
                });
            }
        }
    }
    async getDefaultUserRole(locale, tx) {
        const role = await tx.role.findFirst({
            where: { slug: DEFAULT_ROLE_SLUG },
            select: { id: true },
        });
        if (!role) {
            throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('defaultRoleNotFound', locale, 'Default user role not found.'));
        }
        return role;
    }
    generateRandomPassword(length = RANDOM_PASSWORD_LENGTH) {
        const cryptoObj = globalThis.crypto;
        const groups = [
            PASSWORD_CHARSETS.lowercase,
            PASSWORD_CHARSETS.uppercase,
            PASSWORD_CHARSETS.numbers,
            PASSWORD_CHARSETS.symbols,
        ];
        const allChars = groups.join('');
        const values = new Uint32Array(length + groups.length);
        cryptoObj.getRandomValues(values);
        const passwordChars = [];
        groups.forEach((group, index) => {
            const randomIndex = values[index] % group.length;
            passwordChars.push(group[randomIndex]);
        });
        for (let i = groups.length; i < values.length; i++) {
            const randomIndex = values[i] % allChars.length;
            passwordChars.push(allChars[randomIndex]);
        }
        for (let i = passwordChars.length - 1; i > 0; i--) {
            const randomIndex = values[i] % (i + 1);
            const temp = passwordChars[i];
            passwordChars[i] = passwordChars[randomIndex];
            passwordChars[randomIndex] = temp;
        }
        return passwordChars.slice(0, length).join('');
    }
    getUserIncludeClause() {
        return {
            user_account: true,
            user_credential: true,
            user_identifier: {
                where: { enabled: true },
            },
            user_mfa: {
                where: { verified_at: { not: null } },
            },
            user_activity: {
                orderBy: { created_at: 'desc' },
            },
            user_session: {
                where: {
                    revoked_at: null,
                    expires_at: { gt: new Date() },
                },
                orderBy: { created_at: 'desc' },
            },
        };
    }
    async getUserStatistics() {
        const sevenDaysAgo = new Date(Date.now() - NEW_USERS_PERIOD_DAYS * DAYS_IN_MS);
        const now = new Date();
        const [totalUsers, newUsersLast7Days, blockedUsers] = await this.prismaService.$transaction([
            this.prismaService.user.count(),
            this.prismaService.user.count({
                where: { created_at: { gte: sevenDaysAgo } },
            }),
            this.prismaService.user.count({
                where: { suspended_until: { gt: now } },
            }),
        ]);
        return {
            total: totalUsers,
            newLast7Days: newUsersLast7Days,
            blocked: blockedUsers,
        };
    }
    async getAllRolesWithLocale() {
        return this.prismaService.role.findMany({
            include: {
                role_locale: {
                    include: { locale: true },
                },
            },
        });
    }
    async getUserAssignedRoles(userId) {
        return this.prismaService.role_user.findMany({
            where: { user_id: userId },
            include: {
                role: {
                    include: {
                        role_locale: {
                            include: { locale: true },
                        },
                    },
                },
            },
        });
    }
    mapRolesWithAssignment(allRoles, userRoles) {
        return allRoles.map((role) => {
            const isAssigned = userRoles.some((userRole) => userRole.role_id === role.id);
            const localeData = role.role_locale.find((rl) => rl.locale.code === DEFAULT_LOCALE);
            return {
                id: role.id,
                slug: role.slug,
                name: (localeData === null || localeData === void 0 ? void 0 : localeData.name) || '',
                description: (localeData === null || localeData === void 0 ? void 0 : localeData.description) || '',
                isAssigned,
            };
        });
    }
    async findRoleUser(userId, roleId) {
        return this.prismaService.role_user.findFirst({
            where: {
                user_id: userId,
                role_id: roleId,
            },
        });
    }
};
exports.UserService = UserService;
exports.UserService = UserService = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, common_1.Inject)((0, common_1.forwardRef)(() => api_prisma_1.PrismaService))),
    __param(1, (0, common_1.Inject)((0, common_1.forwardRef)(() => api_pagination_1.PaginationService))),
    __param(2, (0, common_1.Inject)((0, common_1.forwardRef)(() => security_service_1.SecurityService))),
    __param(3, (0, common_1.Inject)((0, common_1.forwardRef)(() => challenge_service_1.ChallengeService))),
    __param(4, (0, common_1.Inject)((0, common_1.forwardRef)(() => file_service_1.FileService))),
    __param(5, (0, common_1.Inject)((0, common_1.forwardRef)(() => integration_developer_api_service_1.IntegrationDeveloperApiService))),
    __metadata("design:paramtypes", [api_prisma_1.PrismaService,
        api_pagination_1.PaginationService,
        security_service_1.SecurityService,
        challenge_service_1.ChallengeService,
        file_service_1.FileService,
        integration_developer_api_service_1.IntegrationDeveloperApiService])
], UserService);
//# sourceMappingURL=user.service.js.map