import { randomUUID } from 'node:crypto';
import { ConflictError, ForbiddenError, NotFoundError } from '../domain/errors.js';
import { normalizeUsername } from '../domain/normalization.js';
import { appendAudit } from './audit.js';
import { generateTemporaryPassword, hashPassword } from './passwords.js';
import { getAuthorization, PERMISSIONS } from './rbac.js';
async function assertManage(tx, actorId) {
    const auth = await getAuthorization(tx, actorId);
    if (!auth.permissions.has(PERMISSIONS.USERS_MANAGE))
        throw new ForbiddenError();
    return auth;
}
async function wouldRemoveLastSuperAdmin(tx, userId) {
    const target = await tx.query(`SELECT u.active, u.archived_at,
      EXISTS(SELECT 1 FROM user_roles ur JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.code = 'super_admin') AS is_super
     FROM users u WHERE u.id = $1`, [userId]);
    if (!target.rowCount || !target.rows[0].is_super || !target.rows[0].active || target.rows[0].archived_at)
        return false;
    const count = await tx.query(`SELECT COUNT(DISTINCT u.id) AS count FROM users u JOIN user_roles ur ON ur.user_id = u.id JOIN roles r ON r.id = ur.role_id
     WHERE r.code = 'super_admin' AND u.active = true AND u.archived_at IS NULL`, []);
    return Number(count.rows[0].count) <= 1;
}
export class UserService {
    db;
    constructor(db) {
        this.db = db;
    }
    async create(actorId, input, audit) {
        return this.db.transaction(async (tx) => {
            const auth = await assertManage(tx, actorId);
            const normalized = normalizeUsername(input.username);
            if (normalized.length < 3)
                throw new ConflictError('نام کاربری باید حداقل سه نویسه باشد.');
            if (!input.roleCodes.length)
                throw new ConflictError('حداقل یک نقش باید انتخاب شود.');
            const duplicate = await tx.query('SELECT id FROM users WHERE username_normalized = $1', [normalized]);
            if (duplicate.rowCount)
                throw new ConflictError('نام کاربری تکراری است.');
            const roles = await tx.query('SELECT id, code FROM roles WHERE code = ANY($1::text[])', [input.roleCodes]);
            if (roles.rowCount !== new Set(input.roleCodes).size)
                throw new ConflictError('یکی از نقش‌ها معتبر نیست.');
            const id = randomUUID();
            const temporaryPassword = generateTemporaryPassword();
            await tx.query(`INSERT INTO users
         (id, branch_id, warehouse_id, name, username, username_normalized, email, password_hash)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`, [id, input.branchId ?? null, input.warehouseId ?? null, input.name.trim(), input.username.trim(), normalized,
                input.email?.trim() || null, await hashPassword(temporaryPassword)]);
            for (const role of roles.rows)
                await tx.query('INSERT INTO user_roles(user_id, role_id) VALUES ($1,$2)', [id, role.id]);
            await appendAudit(tx, { ...audit, actorId, actorRole: auth.roles.join(',') }, {
                action: 'user.created', entityType: 'user', entityId: id,
                newValues: { name: input.name, username: input.username, roles: input.roleCodes, forcedChange: true }
            });
            return { id, temporaryPassword };
        });
    }
    async setActive(actorId, userId, active, audit) {
        await this.db.transaction(async (tx) => {
            const auth = await assertManage(tx, actorId);
            if (!active && await wouldRemoveLastSuperAdmin(tx, userId))
                throw new ConflictError('غیرفعال‌کردن آخرین مدیر کل فعال مجاز نیست.', 'LAST_SUPER_ADMIN');
            const changed = await tx.query(`UPDATE users SET active = $2, session_version = CASE WHEN $2 THEN session_version ELSE session_version + 1 END,
          updated_at = now() WHERE id = $1`, [userId, active]);
            if (!changed.rowCount)
                throw new NotFoundError('کاربر یافت نشد.');
            await appendAudit(tx, { ...audit, actorId, actorRole: auth.roles.join(',') }, {
                action: active ? 'user.activated' : 'user.deactivated', entityType: 'user', entityId: userId,
                oldValues: { active: !active }, newValues: { active }
            });
        });
    }
    async setRoles(actorId, userId, roleCodes, audit) {
        await this.db.transaction(async (tx) => {
            const auth = await assertManage(tx, actorId);
            const current = await tx.query(`SELECT r.code FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = $1`, [userId]);
            if (current.rows.some((role) => role.code === 'super_admin') && !roleCodes.includes('super_admin') && await wouldRemoveLastSuperAdmin(tx, userId)) {
                throw new ConflictError('حذف نقش آخرین مدیر کل فعال مجاز نیست.', 'LAST_SUPER_ADMIN');
            }
            const roles = await tx.query('SELECT id FROM roles WHERE code = ANY($1::text[])', [roleCodes]);
            if (roles.rowCount !== new Set(roleCodes).size)
                throw new ConflictError('نقش نامعتبر است.');
            await tx.query('DELETE FROM user_roles WHERE user_id = $1', [userId]);
            for (const role of roles.rows)
                await tx.query('INSERT INTO user_roles(user_id, role_id) VALUES ($1,$2)', [userId, role.id]);
            await appendAudit(tx, { ...audit, actorId, actorRole: auth.roles.join(',') }, {
                action: 'user.roles_changed', entityType: 'user', entityId: userId,
                oldValues: { roles: current.rows.map((r) => r.code) }, newValues: { roles: roleCodes }
            });
        });
    }
}
//# sourceMappingURL=user-service.js.map