/**
 * SessionTimeout -- Enforces session timeout policies.
 *
 * HIPAA requires automatic session timeout (15-minute inactivity default).
 * SOC 2, PCI DSS, and DORA also require session management.
 *
 * Features:
 *   - Configurable idle timeout per compliance framework
 *   - Absolute session duration limits
 *   - Activity tracking and heartbeat
 *   - Automatic session invalidation
 *   - Audit trail of session events
 *
 * T-052: Session timeout enforcement.
 *
 * @connexum/ai-governance
 */
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import { createLogger } from '../util/log.js';
const log = createLogger('session-timeout');
/**
 * Preset timeout configs per compliance framework.
 *
 * When NODE_ENV=production, uses compliant production values.
 * Otherwise, uses shorter dev/testing values for faster iteration.
 */
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
export const TIMEOUT_PRESETS = IS_PRODUCTION
    ? {
        hipaa: { idleTimeoutMs: 15 * 60 * 1000, maxDurationMs: 8 * 60 * 60 * 1000 },
        'pci-dss': { idleTimeoutMs: 15 * 60 * 1000, maxDurationMs: 12 * 60 * 60 * 1000 },
        soc2: { idleTimeoutMs: 30 * 60 * 1000, maxDurationMs: 24 * 60 * 60 * 1000 },
        dora: { idleTimeoutMs: 15 * 60 * 1000, maxDurationMs: 8 * 60 * 60 * 1000 },
        default: { idleTimeoutMs: 30 * 60 * 1000, maxDurationMs: 24 * 60 * 60 * 1000 },
    }
    : {
        // DEV MODE: shortened values for development/testing
        hipaa: { idleTimeoutMs: 10 * 60 * 1000, maxDurationMs: 30 * 60 * 1000 },
        'pci-dss': { idleTimeoutMs: 10 * 60 * 1000, maxDurationMs: 30 * 60 * 1000 },
        soc2: { idleTimeoutMs: 10 * 60 * 1000, maxDurationMs: 30 * 60 * 1000 },
        dora: { idleTimeoutMs: 10 * 60 * 1000, maxDurationMs: 30 * 60 * 1000 },
        default: { idleTimeoutMs: 10 * 60 * 1000, maxDurationMs: 30 * 60 * 1000 },
    };
export class SessionTimeout {
    config;
    sessionsFile;
    eventsFile;
    checker = null;
    constructor(config) {
        this.config = {
            idleTimeoutMs: IS_PRODUCTION ? 15 * 60 * 1000 : 10 * 60 * 1000,
            maxDurationMs: IS_PRODUCTION ? 8 * 60 * 60 * 1000 : 30 * 60 * 1000,
            checkIntervalMs: 60 * 1000,
            ...config,
        };
        if (!fs.existsSync(config.sessionDir)) {
            fs.mkdirSync(config.sessionDir, { recursive: true });
        }
        this.sessionsFile = path.join(config.sessionDir, 'active-sessions.json');
        this.eventsFile = path.join(config.sessionDir, 'session-events.jsonl');
    }
    /**
     * Create a new managed session.
     */
    createSession(operatorId, metadata) {
        const now = new Date();
        const session = {
            id: `sess_${crypto.randomBytes(12).toString('hex')}`,
            operatorId,
            startedAt: now.toISOString(),
            lastActivity: now.toISOString(),
            expiresAt: new Date(now.getTime() + this.config.idleTimeoutMs).toISOString(),
            absoluteExpiry: new Date(now.getTime() + this.config.maxDurationMs).toISOString(),
            active: true,
            metadata,
        };
        const sessions = this.loadSessions();
        sessions.push(session);
        this.saveSessions(sessions);
        this.logEvent(session.id, 'CREATED', `Operator: ${operatorId}`);
        return session;
    }
    /**
     * Record activity on a session (resets idle timer).
     */
    heartbeat(sessionId) {
        const sessions = this.loadSessions();
        const session = sessions.find(s => s.id === sessionId);
        if (!session || !session.active)
            return false;
        const now = new Date();
        // Check absolute expiry
        if (now.getTime() > new Date(session.absoluteExpiry).getTime()) {
            session.active = false;
            this.saveSessions(sessions);
            this.logEvent(sessionId, 'EXPIRED_ABSOLUTE');
            return false;
        }
        // Check idle expiry
        if (now.getTime() > new Date(session.expiresAt).getTime()) {
            session.active = false;
            this.saveSessions(sessions);
            this.logEvent(sessionId, 'EXPIRED_IDLE');
            return false;
        }
        // Reset idle timer
        session.lastActivity = now.toISOString();
        session.expiresAt = new Date(now.getTime() + this.config.idleTimeoutMs).toISOString();
        this.saveSessions(sessions);
        return true;
    }
    /**
     * Validate a session (check if still active and not expired).
     */
    validate(sessionId) {
        const sessions = this.loadSessions();
        const session = sessions.find(s => s.id === sessionId);
        if (!session || !session.active)
            return null;
        const now = Date.now();
        if (now > new Date(session.absoluteExpiry).getTime()) {
            session.active = false;
            this.saveSessions(sessions);
            this.logEvent(sessionId, 'EXPIRED_ABSOLUTE');
            return null;
        }
        if (now > new Date(session.expiresAt).getTime()) {
            session.active = false;
            this.saveSessions(sessions);
            this.logEvent(sessionId, 'EXPIRED_IDLE');
            return null;
        }
        return session;
    }
    /**
     * Terminate a session explicitly.
     */
    terminate(sessionId) {
        const sessions = this.loadSessions();
        const session = sessions.find(s => s.id === sessionId);
        if (!session)
            return false;
        session.active = false;
        this.saveSessions(sessions);
        this.logEvent(sessionId, 'TERMINATED');
        return true;
    }
    /**
     * Get all active sessions.
     */
    getActiveSessions() {
        return this.loadSessions().filter(s => s.active);
    }
    /**
     * Get sessions for a specific operator.
     */
    getOperatorSessions(operatorId) {
        return this.loadSessions().filter(s => s.operatorId === operatorId && s.active);
    }
    /**
     * Expire all timed-out sessions. Returns count of expired sessions.
     */
    expireStale() {
        const sessions = this.loadSessions();
        const now = Date.now();
        let expired = 0;
        for (const session of sessions) {
            if (!session.active)
                continue;
            if (now > new Date(session.absoluteExpiry).getTime()) {
                session.active = false;
                this.logEvent(session.id, 'EXPIRED_ABSOLUTE');
                expired++;
            }
            else if (now > new Date(session.expiresAt).getTime()) {
                session.active = false;
                this.logEvent(session.id, 'EXPIRED_IDLE');
                expired++;
            }
        }
        if (expired > 0) {
            this.saveSessions(sessions);
            log.info('Expired stale sessions', { count: expired });
        }
        return expired;
    }
    /**
     * Start periodic session cleanup.
     */
    startMonitor() {
        if (this.checker)
            return;
        this.checker = setInterval(() => this.expireStale(), this.config.checkIntervalMs);
    }
    /**
     * Stop the session monitor.
     */
    stopMonitor() {
        if (this.checker) {
            clearInterval(this.checker);
            this.checker = null;
        }
    }
    /**
     * Get session statistics.
     */
    getStats() {
        const sessions = this.loadSessions();
        return {
            activeSessions: sessions.filter(s => s.active).length,
            totalSessions: sessions.length,
            idleTimeoutMs: this.config.idleTimeoutMs,
            maxDurationMs: this.config.maxDurationMs,
        };
    }
    /**
     * Create config from a compliance preset.
     */
    static fromPreset(preset, sessionDir) {
        const p = TIMEOUT_PRESETS[preset] || TIMEOUT_PRESETS['default'];
        return { sessionDir, ...p };
    }
    // --- Private ---
    loadSessions() {
        if (!fs.existsSync(this.sessionsFile))
            return [];
        try {
            return JSON.parse(fs.readFileSync(this.sessionsFile, 'utf-8'));
        }
        catch {
            return [];
        }
    }
    saveSessions(sessions) {
        fs.writeFileSync(this.sessionsFile, JSON.stringify(sessions, null, 2));
    }
    logEvent(sessionId, event, details) {
        const entry = {
            sessionId,
            event,
            timestamp: new Date().toISOString(),
            details,
        };
        try {
            fs.appendFileSync(this.eventsFile, JSON.stringify(entry) + '\n');
        }
        catch {
            log.error('Failed to log session event', { sessionId, event });
        }
    }
}
//# sourceMappingURL=session-timeout.js.map