"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); }
};
var AuthService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthService = void 0;
const common_1 = require("@nestjs/common");
const jwt_1 = require("@nestjs/jwt");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const crypto_1 = require("crypto");
const config_service_1 = require("../common/config/config.service");
const encryption_util_1 = require("../common/utils/encryption.util");
const user_entity_1 = require("../database/entities/user.entity");
const role_entity_1 = require("../database/entities/role.entity");
const permission_entity_1 = require("../database/entities/permission.entity");
const role_permission_entity_1 = require("../database/entities/role-permission.entity");
const MAX_LOGIN_ATTEMPTS = 5;
const LOCK_DURATION_MINUTES = 15;
let AuthService = AuthService_1 = class AuthService {
    constructor(userRepository, roleRepository, permissionRepository, rolePermissionRepository, jwtService, configService) {
        this.userRepository = userRepository;
        this.roleRepository = roleRepository;
        this.permissionRepository = permissionRepository;
        this.rolePermissionRepository = rolePermissionRepository;
        this.jwtService = jwtService;
        this.configService = configService;
        this.logger = new common_1.Logger(AuthService_1.name);
        this.invalidatedRefreshTokens = new Set();
        this.resetTokenStore = new Map();
    }
    async login(email, password) {
        const user = await this.userRepository.findOne({
            where: { email },
            relations: ['role', 'role.permissions', 'business'],
        });
        if (!user) {
            throw new common_1.UnauthorizedException('Invalid email or password');
        }
        if (!user.isActive) {
            throw new common_1.UnauthorizedException('Account is deactivated');
        }
        if (user.lockedUntil && new Date(user.lockedUntil) > new Date()) {
            const remainingMinutes = Math.ceil((user.lockedUntil.getTime() - Date.now()) / 60000);
            throw new common_1.UnauthorizedException(`Account is locked. Try again in ${remainingMinutes} minutes`);
        }
        const isPasswordValid = await (0, encryption_util_1.comparePassword)(password, user.passwordHash);
        if (!isPasswordValid) {
            await this.recordLoginAttempt(user.id, false);
            throw new common_1.UnauthorizedException('Invalid email or password');
        }
        await this.recordLoginAttempt(user.id, true);
        user.lastLoginAt = new Date();
        await this.userRepository.save(user);
        const tokens = this.generateTokens(user);
        const permissions = user.role?.permissions?.map((p) => p.name) ?? [];
        return {
            user: {
                id: user.id,
                email: user.email,
                firstName: user.firstName,
                lastName: user.lastName,
                phoneNumber: user.phoneNumber,
                roleId: user.roleId,
                businessId: user.businessId,
                role: user.role,
                business: user.business,
                isActive: user.isActive,
                twoFactorEnabled: user.twoFactorEnabled,
                lastLoginAt: user.lastLoginAt,
                createdAt: user.createdAt,
            },
            ...tokens,
        };
    }
    async register(dto) {
        const existingUser = await this.userRepository.findOne({
            where: { email: dto.email },
        });
        if (existingUser) {
            throw new common_1.ConflictException('Email already registered');
        }
        const passwordHash = await (0, encryption_util_1.hashPassword)(dto.password);
        const customerRole = await this.roleRepository.findOne({
            where: { type: 'CUSTOMER' },
        });
        const user = this.userRepository.create({
            email: dto.email,
            passwordHash,
            firstName: dto.firstName,
            lastName: dto.lastName,
            phoneNumber: dto.phoneNumber || undefined,
            roleId: customerRole?.id ?? '',
            isActive: true,
        });
        return this.userRepository.save(user);
    }
    async validateUser(userId) {
        const user = await this.userRepository.findOne({
            where: { id: userId, isActive: true },
            relations: ['role', 'role.permissions'],
        });
        if (!user) {
            throw new common_1.NotFoundException('User not found');
        }
        return user;
    }
    generateTokens(user) {
        const permissions = user.role?.permissions?.map((p) => p.name) ?? [];
        const payload = {
            sub: user.id,
            email: user.email,
            role: user.role?.type ?? user.role?.name,
            businessId: user.businessId,
            permissions,
        };
        const accessToken = this.jwtService.sign(payload);
        const refreshPayload = {
            sub: user.id,
            email: user.email,
            tokenType: 'refresh',
            jti: (0, crypto_1.randomBytes)(16).toString('hex'),
        };
        const refreshToken = this.jwtService.sign(refreshPayload, {
            expiresIn: this.configService.get('JWT_REFRESH_EXPIRY', '7d'),
        });
        return { accessToken, refreshToken };
    }
    async refreshToken(token) {
        let payload;
        try {
            payload = this.jwtService.verify(token);
        }
        catch {
            throw new common_1.UnauthorizedException('Invalid or expired refresh token');
        }
        if (payload.tokenType !== 'refresh') {
            throw new common_1.UnauthorizedException('Invalid token type');
        }
        if (this.invalidatedRefreshTokens.has(payload.jti)) {
            throw new common_1.UnauthorizedException('Refresh token has been revoked');
        }
        const user = await this.userRepository.findOne({
            where: { id: payload.sub, isActive: true },
            relations: ['role', 'role.permissions'],
        });
        if (!user) {
            throw new common_1.UnauthorizedException('User not found');
        }
        this.invalidatedRefreshTokens.add(payload.jti);
        return this.generateTokens(user);
    }
    async changePassword(userId, oldPassword, newPassword) {
        const user = await this.userRepository.findOne({
            where: { id: userId },
        });
        if (!user) {
            throw new common_1.NotFoundException('User not found');
        }
        const isOldPasswordValid = await (0, encryption_util_1.comparePassword)(oldPassword, user.passwordHash);
        if (!isOldPasswordValid) {
            throw new common_1.BadRequestException('Current password is incorrect');
        }
        user.passwordHash = await (0, encryption_util_1.hashPassword)(newPassword);
        user.passwordChangedAt = new Date();
        await this.userRepository.save(user);
        this.logger.log(`Password changed for user ${userId}`);
    }
    async resetPassword(token, newPassword) {
        const storedData = this.resetTokenStore.get(token);
        if (!storedData) {
            throw new common_1.BadRequestException('Invalid or expired reset token');
        }
        if (new Date() > storedData.expiresAt) {
            this.resetTokenStore.delete(token);
            throw new common_1.BadRequestException('Reset token has expired');
        }
        const user = await this.userRepository.findOne({
            where: { id: storedData.userId },
        });
        if (!user) {
            throw new common_1.NotFoundException('User not found');
        }
        user.passwordHash = await (0, encryption_util_1.hashPassword)(newPassword);
        user.passwordChangedAt = new Date();
        user.loginAttempts = 0;
        user.lockedUntil = null;
        await this.userRepository.save(user);
        this.resetTokenStore.delete(token);
        this.logger.log(`Password reset for user ${user.id}`);
    }
    async forgotPassword(email) {
        const user = await this.userRepository.findOne({ where: { email } });
        if (!user) {
            return;
        }
        const resetToken = (0, crypto_1.randomBytes)(32).toString('hex');
        const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
        this.resetTokenStore.set(resetToken, {
            userId: user.id,
            expiresAt,
        });
        this.logger.log(`Password reset token generated for ${email}: ${resetToken}`);
    }
    async enable2FA(userId) {
        const user = await this.userRepository.findOne({
            where: { id: userId },
        });
        if (!user) {
            throw new common_1.NotFoundException('User not found');
        }
        if (user.twoFactorEnabled) {
            throw new common_1.BadRequestException('2FA is already enabled');
        }
        const secret = this.generateTOTPSecret();
        user.twoFactorSecret = secret;
        await this.userRepository.save(user);
        const qrCodeUrl = `otpauth://totp/HBS:${encodeURIComponent(user.email)}?secret=${secret}&issuer=HBS`;
        return { secret, qrCodeUrl };
    }
    async verify2FA(userId, token) {
        const user = await this.userRepository.findOne({
            where: { id: userId },
        });
        if (!user) {
            throw new common_1.NotFoundException('User not found');
        }
        if (!user.twoFactorSecret) {
            throw new common_1.BadRequestException('2FA not set up');
        }
        const isValid = this.verifyTOTP(user.twoFactorSecret, token);
        if (isValid && !user.twoFactorEnabled) {
            user.twoFactorEnabled = true;
            await this.userRepository.save(user);
        }
        return isValid;
    }
    async logout(userId) {
        const user = await this.userRepository.findOne({
            where: { id: userId },
        });
        if (!user) {
            throw new common_1.NotFoundException('User not found');
        }
        this.logger.log(`User ${userId} logged out`);
    }
    async recordLoginAttempt(userId, success) {
        const user = await this.userRepository.findOne({
            where: { id: userId },
        });
        if (!user)
            return;
        if (success) {
            user.loginAttempts = 0;
            user.lockedUntil = null;
        }
        else {
            user.loginAttempts = (user.loginAttempts || 0) + 1;
            if (user.loginAttempts >= MAX_LOGIN_ATTEMPTS) {
                user.lockedUntil = new Date(Date.now() + LOCK_DURATION_MINUTES * 60 * 1000);
                this.logger.warn(`User ${userId} locked until ${user.lockedUntil}`);
            }
        }
        await this.userRepository.save(user);
    }
    generateTOTPSecret() {
        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
        let secret = '';
        const bytes = (0, crypto_1.randomBytes)(20);
        for (let i = 0; i < 20; i++) {
            secret += chars[bytes[i] % 32];
        }
        return secret;
    }
    verifyTOTP(secret, token) {
        const now = Math.floor(Date.now() / 1000);
        const timeStep = 30;
        for (let i = -1; i <= 1; i++) {
            const counter = Math.floor((now + i * timeStep) / timeStep);
            if (this.computeTOTP(secret, counter) === token) {
                return true;
            }
        }
        return false;
    }
    computeTOTP(secret, counter) {
        return this.hotp(secret, counter, 6);
    }
    hotp(secret, counter, digits) {
        const secretBuffer = this.base32ToBuffer(secret);
        const counterBuffer = Buffer.alloc(8);
        counterBuffer.writeBigInt64BE(BigInt(counter), 0);
        const hmac = (0, crypto_1.createHmac)('sha1', secretBuffer);
        hmac.update(counterBuffer);
        const hmacResult = hmac.digest();
        const offset = hmacResult[hmacResult.length - 1] & 0xf;
        const binary = ((hmacResult[offset] & 0x7f) << 24) |
            ((hmacResult[offset + 1] & 0xff) << 16) |
            ((hmacResult[offset + 2] & 0xff) << 8) |
            (hmacResult[offset + 3] & 0xff);
        const otp = binary % Math.pow(10, digits);
        return otp.toString().padStart(digits, '0');
    }
    base32ToBuffer(base32) {
        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
        let bits = '';
        for (let i = 0; i < base32.length; i++) {
            const val = chars.indexOf(base32[i].toUpperCase());
            if (val === -1)
                continue;
            bits += val.toString(2).padStart(5, '0');
        }
        const bytes = [];
        for (let i = 0; i + 8 <= bits.length; i += 8) {
            bytes.push(parseInt(bits.substring(i, i + 8), 2));
        }
        return Buffer.from(bytes);
    }
};
exports.AuthService = AuthService;
exports.AuthService = AuthService = AuthService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(user_entity_1.User)),
    __param(1, (0, typeorm_1.InjectRepository)(role_entity_1.Role)),
    __param(2, (0, typeorm_1.InjectRepository)(permission_entity_1.Permission)),
    __param(3, (0, typeorm_1.InjectRepository)(role_permission_entity_1.RolePermission)),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        jwt_1.JwtService,
        config_service_1.ConfigService])
], AuthService);
