"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.RedisSessionRepository = exports.RedisSessionStore = void 0;
const common_1 = require("@nestjs/common");
const base_session_repository_1 = require("./base-session.repository");
const uuid_1 = require("uuid");
let RedisSessionStore = class RedisSessionStore extends base_session_repository_1.BaseSessionRepository {
    keyPrefix;
    userSessionsPrefix;
    defaultTtlSeconds;
    redis;
    constructor(redisClient, options = {}) {
        super();
        if (!redisClient) {
            throw new Error('RedisSessionStore requires the ioredis client. Install with: npm install ioredis');
        }
        this.redis = redisClient;
        this.keyPrefix = this.normalizePrefix(options.keyPrefix || 'nest-auth:sess:');
        this.userSessionsPrefix = `${this.keyPrefix}user:`;
        this.defaultTtlSeconds = options.ttlSeconds;
    }
    normalizePrefix(prefix) {
        if (!prefix)
            return '';
        return prefix.endsWith(':') ? prefix : `${prefix}:`;
    }
    getSessionKey(sessionId) {
        return `${this.keyPrefix}${sessionId}`;
    }
    getUserSessionsKey(userId) {
        return `${this.userSessionsPrefix}${userId}`;
    }
    getTtlSeconds(expiresAt) {
        if (expiresAt instanceof Date) {
            const ttl = Math.floor((expiresAt.getTime() - Date.now()) / 1000);
            if (ttl > 0) {
                return ttl;
            }
        }
        if (typeof this.defaultTtlSeconds === 'number' && this.defaultTtlSeconds > 0) {
            return this.defaultTtlSeconds;
        }
        return undefined;
    }
    serializeSessionPartial(session) {
        const data = {};
        if (session.userId !== undefined)
            data.userId = session.userId;
        if (session.refreshToken !== undefined)
            data.refreshToken = session.refreshToken;
        if (session.userAgent !== undefined)
            data.userAgent = session.userAgent;
        if (session.deviceName !== undefined)
            data.deviceName = session.deviceName;
        if (session.ipAddress !== undefined)
            data.ipAddress = session.ipAddress;
        if (session.data !== undefined) {
            data.data = session.data === null ? null : JSON.stringify(session.data);
        }
        if (session.expiresAt !== undefined) {
            data.expiresAt = session.expiresAt ? new Date(session.expiresAt).toISOString() : null;
        }
        if (session.lastActive !== undefined) {
            data.lastActive = session.lastActive ? new Date(session.lastActive).toISOString() : null;
        }
        return data;
    }
    async create(session) {
        const sessionId = session.id || (0, uuid_1.v4)();
        const sessionKey = this.getSessionKey(sessionId);
        const userSessionsKey = this.getUserSessionsKey(session.userId);
        const sessionData = {
            id: sessionId,
            userId: session.userId,
            refreshToken: session.refreshToken,
            data: session.data,
            expiresAt: session.expiresAt,
            userAgent: session.userAgent,
            deviceName: session.deviceName,
            ipAddress: session.ipAddress,
            lastActive: session.lastActive || new Date(),
        };
        const { id, ...sessionWithoutId } = sessionData;
        const serialized = this.serializeSession(sessionWithoutId);
        const pipeline = this.redis.pipeline();
        pipeline.hset(sessionKey, serialized);
        pipeline.sadd(userSessionsKey, sessionId);
        const ttlSeconds = this.getTtlSeconds(session.expiresAt);
        if (ttlSeconds) {
            pipeline.expire(sessionKey, ttlSeconds);
        }
        const results = await pipeline.exec();
        if (results) {
            const errors = results.filter(([err]) => err);
            if (errors.length > 0) {
                const errorMessages = errors.map(([err]) => err?.message || String(err)).join(', ');
                throw new Error(`Redis pipeline failed during session creation: ${errorMessages}`);
            }
        }
        return sessionData;
    }
    async findById(sessionId) {
        try {
            const sessionKey = this.getSessionKey(sessionId);
            const data = await this.redis.hgetall(sessionKey);
            if (!data || Object.keys(data).length === 0) {
                return null;
            }
            const session = this.deserializeSession(data);
            session.id = sessionId;
            if (this.isExpired(session)) {
                await this.delete(sessionId);
                return null;
            }
            return session;
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to retrieve session from Redis: ${errorMessage}`);
        }
    }
    async findByUserId(userId) {
        try {
            const userSessionsKey = this.getUserSessionsKey(userId);
            const sessionIds = await this.redis.smembers(userSessionsKey);
            if (!sessionIds.length) {
                return [];
            }
            const pipeline = this.redis.pipeline();
            for (const sessionId of sessionIds) {
                pipeline.hgetall(this.getSessionKey(sessionId));
            }
            const results = await pipeline.exec();
            if (!results) {
                return [];
            }
            const errors = results.filter(([err]) => err);
            if (errors.length > 0) {
                const errorMessages = errors.map(([err]) => err?.message || String(err)).join(', ');
                throw new Error(`Redis pipeline failed during findByUserId: ${errorMessages}`);
            }
            const sessions = [];
            const staleIds = [];
            results.forEach(([, data], index) => {
                if (!data || Object.keys(data).length === 0) {
                    staleIds.push(sessionIds[index]);
                    return;
                }
                const session = this.deserializeSession(data);
                session.id = sessionIds[index];
                if (this.isExpired(session)) {
                    staleIds.push(sessionIds[index]);
                    return;
                }
                sessions.push(session);
            });
            if (staleIds.length) {
                try {
                    await this.redis.srem(userSessionsKey, ...staleIds);
                    const cleanup = this.redis.pipeline();
                    staleIds.forEach((sessionId) => cleanup.del(this.getSessionKey(sessionId)));
                    await cleanup.exec();
                }
                catch (cleanupError) {
                    console.warn('Failed to cleanup stale session IDs:', cleanupError);
                }
            }
            return sessions;
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to retrieve user sessions from Redis: ${errorMessage}`);
        }
    }
    async findActiveByUserId(userId) {
        const allSessions = await this.findByUserId(userId);
        return this.filterActive(allSessions);
    }
    async update(sessionId, updates) {
        try {
            const sessionKey = this.getSessionKey(sessionId);
            const { id, ...updatesWithoutId } = updates;
            const serialized = this.serializeSessionPartial(updatesWithoutId);
            if (Object.keys(serialized).length === 0) {
                return await this.findById(sessionId);
            }
            const pipeline = this.redis.pipeline();
            pipeline.hset(sessionKey, serialized);
            if (updates.expiresAt) {
                const ttlSeconds = this.getTtlSeconds(new Date(updates.expiresAt));
                if (ttlSeconds) {
                    pipeline.expire(sessionKey, ttlSeconds);
                }
            }
            const results = await pipeline.exec();
            if (results) {
                const errors = results.filter(([err]) => err);
                if (errors.length > 0) {
                    const errorMessages = errors.map(([err]) => err?.message || String(err)).join(', ');
                    throw new Error(`Redis pipeline failed during session update: ${errorMessages}`);
                }
            }
            return await this.findById(sessionId);
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to update session in Redis: ${errorMessage}`);
        }
    }
    async delete(sessionId) {
        try {
            const session = await this.findById(sessionId);
            if (!session)
                return;
            const sessionKey = this.getSessionKey(sessionId);
            const userSessionsKey = this.getUserSessionsKey(session.userId);
            await this.redis.del(sessionKey);
            await this.redis.srem(userSessionsKey, sessionId);
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to delete session from Redis: ${errorMessage}`);
        }
    }
    async deleteByUserId(userId) {
        try {
            const userSessionsKey = this.getUserSessionsKey(userId);
            const sessionIds = await this.redis.smembers(userSessionsKey);
            if (!sessionIds.length) {
                return;
            }
            const pipeline = this.redis.pipeline();
            for (const sessionId of sessionIds) {
                pipeline.del(this.getSessionKey(sessionId));
            }
            pipeline.del(userSessionsKey);
            const results = await pipeline.exec();
            if (results) {
                const errors = results.filter(([err]) => err);
                if (errors.length > 0) {
                    const errorMessages = errors.map(([err]) => err?.message || String(err)).join(', ');
                    throw new Error(`Redis pipeline failed during deleteByUserId: ${errorMessages}`);
                }
            }
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to delete user sessions from Redis: ${errorMessage}`);
        }
    }
    async deleteExpired() {
        return 0;
    }
    async countActiveByUserId(userId) {
        const activeSessions = await this.findActiveByUserId(userId);
        return activeSessions.length;
    }
    async updateLastActive(sessionId) {
        try {
            await this.update(sessionId, {
                lastActive: new Date(),
            });
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to update last active timestamp in Redis: ${errorMessage}`);
        }
    }
    async healthCheck() {
        try {
            const result = await this.redis.ping();
            return result === 'PONG';
        }
        catch {
            return false;
        }
    }
};
exports.RedisSessionStore = RedisSessionStore;
exports.RedisSessionRepository = RedisSessionStore;
exports.RedisSessionRepository = exports.RedisSessionStore = RedisSessionStore = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [Object, Object])
], RedisSessionStore);
//# sourceMappingURL=redis-session.repository.js.map