/**
 * LogRotation -- Size-based and age-based log file rotation.
 *
 * Prevents unbounded log growth by rotating files when they exceed
 * size or age thresholds. Supports gzip compression of archived logs.
 *
 * T-042: Log rotation for all file-based storage.
 *
 * @connexum/ai-governance
 */
import * as fs from 'fs';
import * as path from 'path';
import * as zlib from 'zlib';
import { createLogger } from './log.js';
const log = createLogger('log-rotation');
export class LogRotation {
    config;
    constructor(config) {
        this.config = {
            maxSizeBytes: 10 * 1024 * 1024, // 10MB
            maxAgeMs: 10 * 60 * 1000, // 10 min (DEV MODE -- production: 7 * 24 * 60 * 60 * 1000 / 7 days)
            maxFiles: 10,
            compress: true,
            ...config,
        };
    }
    /**
     * Check if a file needs rotation and rotate if so.
     */
    rotateIfNeeded(filePath) {
        if (!fs.existsSync(filePath)) {
            return { rotated: false, originalFile: filePath };
        }
        try {
            const stat = fs.statSync(filePath);
            // Check size
            if (stat.size > this.config.maxSizeBytes) {
                return this.rotate(filePath, 'size');
            }
            // Check age
            const age = Date.now() - stat.mtimeMs;
            if (age > this.config.maxAgeMs) {
                return this.rotate(filePath, 'age');
            }
            return { rotated: false, originalFile: filePath };
        }
        catch {
            return { rotated: false, originalFile: filePath };
        }
    }
    /**
     * Force rotation of a file regardless of thresholds.
     */
    rotate(filePath, reason = 'size') {
        if (!fs.existsSync(filePath)) {
            return { rotated: false, originalFile: filePath };
        }
        try {
            const stat = fs.statSync(filePath);
            const dir = path.dirname(filePath);
            const ext = path.extname(filePath);
            const base = path.basename(filePath, ext);
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
            const archiveName = `${base}.${timestamp}${ext}`;
            const archivePath = path.join(dir, archiveName);
            // Move current file to archive
            fs.renameSync(filePath, archivePath);
            // Compress if enabled
            let finalPath = archivePath;
            if (this.config.compress) {
                const gzPath = archivePath + '.gz';
                const input = fs.readFileSync(archivePath);
                const compressed = zlib.gzipSync(input);
                fs.writeFileSync(gzPath, compressed);
                fs.unlinkSync(archivePath);
                finalPath = gzPath;
            }
            // Cleanup old rotated files
            this.cleanup(dir, base, ext);
            log.info('Log rotated', { file: filePath, archive: finalPath, reason });
            return {
                rotated: true,
                originalFile: filePath,
                archivedFile: finalPath,
                originalSizeBytes: stat.size,
                reason,
            };
        }
        catch (err) {
            log.error('Rotation failed', { file: filePath, error: err.message });
            return { rotated: false, originalFile: filePath };
        }
    }
    /**
     * Rotate all matching files in a directory.
     */
    rotateDir(dir, pattern) {
        if (!fs.existsSync(dir))
            return [];
        const results = [];
        try {
            const files = fs.readdirSync(dir);
            for (const file of files) {
                // Skip already-rotated files
                if (file.endsWith('.gz'))
                    continue;
                if (/\.\d{4}-\d{2}-\d{2}/.test(file))
                    continue;
                if (pattern && !pattern.test(file))
                    continue;
                const filePath = path.join(dir, file);
                const stat = fs.statSync(filePath);
                if (!stat.isFile())
                    continue;
                const result = this.rotateIfNeeded(filePath);
                if (result.rotated) {
                    results.push(result);
                }
            }
        }
        catch {
            // Skip inaccessible directories
        }
        return results;
    }
    /**
     * Get rotation status for a file.
     */
    getStatus(filePath) {
        if (!fs.existsSync(filePath)) {
            return { exists: false, sizeBytes: 0, ageMs: 0, needsRotation: false };
        }
        try {
            const stat = fs.statSync(filePath);
            const age = Date.now() - stat.mtimeMs;
            let reason;
            if (stat.size > this.config.maxSizeBytes)
                reason = 'size';
            else if (age > this.config.maxAgeMs)
                reason = 'age';
            return {
                exists: true,
                sizeBytes: stat.size,
                ageMs: age,
                needsRotation: reason !== undefined,
                reason,
            };
        }
        catch {
            return { exists: false, sizeBytes: 0, ageMs: 0, needsRotation: false };
        }
    }
    // --- Private ---
    cleanup(dir, base, ext) {
        try {
            const files = fs.readdirSync(dir)
                .filter(f => f.startsWith(base + '.') && (f.includes(ext) || f.includes(ext + '.gz')))
                .filter(f => f !== base + ext) // exclude the main file
                .sort()
                .reverse();
            // Remove oldest files beyond maxFiles
            if (files.length > this.config.maxFiles) {
                const toRemove = files.slice(this.config.maxFiles);
                for (const file of toRemove) {
                    try {
                        fs.unlinkSync(path.join(dir, file));
                    }
                    catch { /* ignore */ }
                }
            }
        }
        catch { /* ignore */ }
    }
}
//# sourceMappingURL=log-rotation.js.map