"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.AuthService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const jwt_1 = require("@nestjs/jwt");
const client_1 = require("@prisma/client");
const bcrypt = require("bcrypt");
const audit_service_1 = require("../audit/audit.service");
const code_generator_1 = require("../common/code-generator");
const profile_completion_1 = require("../common/profile-completion");
const plan_limits_1 = require("../common/plan-limits");
const estate_scope_1 = require("../common/estate-scope");
const prisma_service_1 = require("../database/prisma.service");
const otp_service_1 = require("./otp.service");
let AuthService = class AuthService {
    prisma;
    jwt;
    config;
    audit;
    otp;
    BCRYPT_ROUNDS = 10;
    constructor(prisma, jwt, config, audit, otp) {
        this.prisma = prisma;
        this.jwt = jwt;
        this.config = config;
        this.audit = audit;
        this.otp = otp;
    }
    async login(dto) {
        const user = await this.getLoginUser(dto.phone);
        return this.otp.sendOtp(dto.phone, user.name);
    }
    async requestConsoleOtp(phone) {
        const user = await this.getLoginUser(phone);
        if (user.role !== client_1.UserRole.SUPER_ADMIN) {
            throw new common_1.UnauthorizedException('Only super admins can access the API console');
        }
        return this.otp.sendOtp(phone, user.name);
    }
    async verifyOtp(dto) {
        const user = await this.getLoginUser(dto.phone);
        if (!this.otp.verifyOtp(dto.phone, dto.otp)) {
            throw new common_1.UnauthorizedException('Invalid OTP');
        }
        await this.prisma.user.update({
            where: { id: user.id },
            data: { phoneVerifiedAt: user.phoneVerifiedAt ?? new Date() }
        });
        return this.issueTokens(user.id, user.estateId, user.role);
    }
    async issueConsoleSession(dto) {
        const user = await this.getLoginUser(dto.phone);
        if (user.role !== client_1.UserRole.SUPER_ADMIN) {
            throw new common_1.UnauthorizedException('Only super admins can access the API console');
        }
        if (!this.otp.verifyOtp(dto.phone, dto.otp)) {
            throw new common_1.UnauthorizedException('Invalid OTP');
        }
        return this.jwt.signAsync({ sub: user.id, estateId: user.estateId, role: user.role, type: 'console' }, {
            secret: this.config.get('JWT_ACCESS_SECRET'),
            expiresIn: this.config.get('CONSOLE_SESSION_TTL', '8h')
        });
    }
    async refresh(dto) {
        try {
            const payload = await this.jwt.verifyAsync(dto.refreshToken, { secret: this.config.get('JWT_REFRESH_SECRET') });
            const user = await this.prisma.user.findUnique({
                where: { id: payload.sub },
                include: { estate: { select: { status: true } } }
            });
            if (!user || !user.refreshToken) {
                throw new common_1.UnauthorizedException('Refresh token not found');
            }
            this.assertActiveAccount(user.status, user.role, user.estate?.status);
            const tokenIsValid = await bcrypt.compare(dto.refreshToken, user.refreshToken);
            if (!tokenIsValid) {
                throw new common_1.UnauthorizedException('Refresh token is invalid or revoked');
            }
            return this.issueTokens(payload.sub, payload.estateId, payload.role);
        }
        catch (error) {
            if (error instanceof common_1.UnauthorizedException) {
                throw error;
            }
            throw new common_1.UnauthorizedException('Invalid refresh token');
        }
    }
    async signup(dto, actor) {
        this.assertCanCreateRole(actor, dto.role);
        const estateId = this.resolveSignupEstate(actor, dto);
        this.assertRoleSpecificFields(dto);
        await this.assertPlanRoleLimit(estateId, dto.role);
        try {
            const result = await this.prisma.$transaction(async (tx) => {
                const account = await tx.user.create({
                    data: {
                        estateId,
                        name: dto.name,
                        phone: dto.phone,
                        firstName: dto.firstName,
                        lastName: dto.lastName,
                        email: dto.email,
                        address: dto.address,
                        role: dto.role
                    }
                });
                if (dto.role === client_1.UserRole.RESIDENT) {
                    const draft = {
                        estateId: estateId ?? this.requiredEstateIdForRole(dto.role),
                        firstName: dto.firstName,
                        lastName: dto.lastName,
                        phone: dto.phone,
                        blockNumber: dto.blockNumber,
                        flatNumber: dto.flatNumber ?? dto.unit,
                        unit: dto.flatNumber ?? dto.unit,
                        address: dto.address,
                        profileConfirmed: false
                    };
                    const completion = (0, profile_completion_1.calculateProfileCompletion)(client_1.UserRole.RESIDENT, draft);
                    const resident = await tx.resident.create({
                        data: {
                            estateId: estateId ?? this.requiredEstateIdForRole(dto.role),
                            userId: account.id,
                            residentCode: `RES-${(0, code_generator_1.generateCode)()}`,
                            name: dto.name,
                            firstName: dto.firstName,
                            lastName: dto.lastName,
                            phone: dto.phone,
                            unit: dto.flatNumber ?? dto.unit ?? '',
                            blockNumber: dto.blockNumber,
                            flatNumber: dto.flatNumber ?? dto.unit,
                            address: dto.address,
                            email: dto.email,
                            profileCompletion: completion.completion
                        }
                    });
                    return { user: account, resident };
                }
                if (dto.role === client_1.UserRole.GUARD) {
                    const draft = {
                        estateId: estateId ?? this.requiredEstateIdForRole(dto.role),
                        firstName: dto.firstName,
                        lastName: dto.lastName,
                        phone: dto.phone,
                        guardCode: `GRD-${(0, code_generator_1.generateCode)()}`,
                        assignedGateId: dto.assignedGateId,
                        address: dto.address,
                        profileConfirmed: false
                    };
                    const completion = (0, profile_completion_1.calculateProfileCompletion)(client_1.UserRole.GUARD, draft);
                    const guard = await tx.guard.create({
                        data: {
                            estateId: estateId ?? this.requiredEstateIdForRole(dto.role),
                            userId: account.id,
                            guardCode: draft.guardCode,
                            name: dto.name,
                            firstName: dto.firstName,
                            lastName: dto.lastName,
                            phone: dto.phone,
                            address: dto.address,
                            assignedGateId: dto.assignedGateId,
                            profileCompletion: completion.completion,
                            deviceId: dto.deviceId
                        }
                    });
                    return { user: account, guard };
                }
                return { user: account };
            });
            await this.audit.record({
                estateId,
                actorType: 'USER',
                actorId: actor.id,
                action: 'USER_SIGNED_UP',
                metadata: {
                    userId: result.user.id,
                    role: result.user.role,
                    phone: result.user.phone
                }
            });
            return result;
        }
        catch (error) {
            if (error instanceof client_1.Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
                throw new common_1.ConflictException('A user with this phone number already exists');
            }
            throw error;
        }
    }
    async issueTokens(userId, estateId, role) {
        const payload = { sub: userId, estateId, role };
        const accessToken = await this.jwt.signAsync(payload, {
            secret: this.config.get('JWT_ACCESS_SECRET'),
            expiresIn: '15m'
        });
        const refreshToken = await this.jwt.signAsync(payload, {
            secret: this.config.get('JWT_REFRESH_SECRET'),
            expiresIn: '30d'
        });
        const hashedRefreshToken = await bcrypt.hash(refreshToken, this.BCRYPT_ROUNDS);
        await this.prisma.user.update({
            where: { id: userId },
            data: { refreshToken: hashedRefreshToken }
        });
        const sessionUser = await this.prisma.user.findUnique({
            where: { id: userId },
            select: {
                id: true,
                name: true,
                firstName: true,
                lastName: true,
                phone: true,
                role: true,
                estateId: true,
                estate: {
                    select: {
                        id: true,
                        name: true,
                        location: true,
                        subscriptionPlan: { select: { code: true, name: true, sortOrder: true } }
                    }
                }
            }
        });
        return {
            accessToken,
            refreshToken,
            user: sessionUser
                ? {
                    id: sessionUser.id,
                    name: sessionUser.name,
                    firstName: sessionUser.firstName,
                    lastName: sessionUser.lastName,
                    phone: sessionUser.phone,
                    role: sessionUser.role,
                    estateId: sessionUser.estateId
                }
                : null,
            estate: sessionUser?.estate ?? null,
            estateName: sessionUser?.estate?.name ?? null,
            estatePlanCode: sessionUser?.estate?.subscriptionPlan?.code ?? null
        };
    }
    async getLoginUser(phone) {
        const user = await this.prisma.user.findUnique({
            where: { phone },
            include: { estate: { select: { status: true } } }
        });
        if (!user) {
            throw new common_1.UnauthorizedException('No account exists for this phone number');
        }
        this.assertActiveAccount(user.status, user.role, user.estate?.status);
        return user;
    }
    assertActiveAccount(status, role, estateStatus) {
        if (status !== client_1.AccountStatus.ACTIVE) {
            throw new common_1.UnauthorizedException('Account is not active');
        }
        if (!this.isPlatformRole(role) && estateStatus !== client_1.EstateStatus.ACTIVE) {
            throw new common_1.UnauthorizedException('Estate is not active');
        }
    }
    assertCanCreateRole(actor, role) {
        if (actor.role === client_1.UserRole.SUPER_ADMIN) {
            return;
        }
        const estateAdminRoles = [client_1.UserRole.SECURITY_SUPERVISOR, client_1.UserRole.GUARD, client_1.UserRole.RESIDENT];
        const supervisorRoles = [client_1.UserRole.GUARD, client_1.UserRole.RESIDENT];
        const allowedRoles = actor.role === client_1.UserRole.ESTATE_ADMIN
            ? estateAdminRoles
            : actor.role === client_1.UserRole.SECURITY_SUPERVISOR
                ? supervisorRoles
                : [];
        if (!allowedRoles.includes(role)) {
            throw new common_1.ForbiddenException(`You cannot create users with role ${role}`);
        }
    }
    resolveSignupEstate(actor, dto) {
        if (this.isPlatformRole(dto.role)) {
            return null;
        }
        return (0, estate_scope_1.resolveEstateScope)(actor, dto.estateId);
    }
    assertRoleSpecificFields(_dto) {
        return;
    }
    async assertPlanRoleLimit(estateId, role) {
        const limitKey = this.roleLimitKey(role);
        if (!estateId || !limitKey) {
            return;
        }
        const estate = await this.prisma.estate.findUnique({
            where: { id: estateId },
            select: { subscriptionPlan: { select: { limits: true } } }
        });
        const limit = (0, plan_limits_1.getNumericPlanLimit)(estate?.subscriptionPlan?.limits, limitKey);
        if (limit === null) {
            return;
        }
        const currentCount = await this.countEstateRole(estateId, role);
        if (currentCount >= limit) {
            throw new common_1.ConflictException(`${this.roleLabel(role)} limit for this estate plan has been reached`);
        }
    }
    roleLimitKey(role) {
        if (role === client_1.UserRole.RESIDENT) {
            return 'residents';
        }
        if (role === client_1.UserRole.GUARD) {
            return 'guards';
        }
        if (role === client_1.UserRole.SECURITY_SUPERVISOR) {
            return 'supervisors';
        }
        if (role === client_1.UserRole.ESTATE_ADMIN) {
            return 'estateAdmins';
        }
        return null;
    }
    countEstateRole(estateId, role) {
        if (role === client_1.UserRole.RESIDENT) {
            return this.prisma.resident.count({ where: { estateId } });
        }
        if (role === client_1.UserRole.GUARD) {
            return this.prisma.guard.count({ where: { estateId } });
        }
        return this.prisma.user.count({ where: { estateId, role } });
    }
    roleLabel(role) {
        return role.toLowerCase().replace(/_/g, ' ');
    }
    requiredEstateIdForRole(role) {
        throw new common_1.BadRequestException(`estateId is required when signing up a ${role.toLowerCase()}`);
    }
    requiredField(field) {
        throw new common_1.BadRequestException(`${field} is required`);
    }
    isPlatformRole(role) {
        return role === client_1.UserRole.SUPER_ADMIN || role === client_1.UserRole.SALES_ADMIN;
    }
};
exports.AuthService = AuthService;
exports.AuthService = AuthService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [prisma_service_1.PrismaService,
        jwt_1.JwtService,
        config_1.ConfigService,
        audit_service_1.AuditService,
        otp_service_1.OtpService])
], AuthService);
//# sourceMappingURL=auth.service.js.map