"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoggingService = exports.LogLevel = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const Minio = __importStar(require("minio"));
var LogLevel;
(function (LogLevel) {
    LogLevel["REQUEST"] = "REQUEST";
    LogLevel["ERROR"] = "ERROR";
    LogLevel["WARNING"] = "WARNING";
    LogLevel["AUTH"] = "AUTH";
    LogLevel["DATABASE"] = "DATABASE";
    LogLevel["STARTUP"] = "STARTUP";
    LogLevel["CONNECTION"] = "CONNECTION";
    LogLevel["OTHER"] = "OTHER";
})(LogLevel || (exports.LogLevel = LogLevel = {}));
class LoggingService {
    constructor() {
        this.minioClient = null;
        this.logBuffer = [];
        this.currentLogFile = null;
        this.logCount = 0;
        this.maxLogsPerFile = parseInt(process.env.MAX_LOGS_PER_FILE || '10000');
        this.logsDir = path_1.default.join(process.cwd(), 'logs');
        this.bucketName = process.env.MINIO_BUCKET_NAME || 'serpentrace-logs';
        this.uploadInterval = null;
        this.initializeLogsDirectory();
        this.initializeMinioClient();
        this.createNewLogFile();
        if (process.env.NODE_ENV !== 'test') {
            this.startPeriodicUpload();
        }
        process.on('SIGTERM', () => this.shutdown());
        process.on('SIGINT', () => this.shutdown());
        process.on('beforeExit', () => this.shutdown());
    }
    static getInstance() {
        if (!LoggingService.instance) {
            LoggingService.instance = new LoggingService();
        }
        return LoggingService.instance;
    }
    initializeLogsDirectory() {
        try {
            if (!fs_1.default.existsSync(this.logsDir)) {
                fs_1.default.mkdirSync(this.logsDir, { recursive: true });
            }
            // Create monthly subdirectory
            const monthlyDir = this.getMonthlyDirectory();
            if (!fs_1.default.existsSync(monthlyDir)) {
                fs_1.default.mkdirSync(monthlyDir, { recursive: true });
            }
        }
        catch (error) {
            console.error('Failed to initialize logs directory:', error);
        }
    }
    initializeMinioClient() {
        try {
            // Check if in production or development
            if (process.env.NODE_ENV === 'production') {
                if (process.env.MINIO_ENDPOINT && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY) {
                    this.minioClient = new Minio.Client({
                        endPoint: process.env.MINIO_ENDPOINT,
                        port: parseInt(process.env.MINIO_PORT || '9000'),
                        useSSL: process.env.MINIO_USE_SSL === 'true',
                        accessKey: process.env.MINIO_ACCESS_KEY,
                        secretKey: process.env.MINIO_SECRET_KEY
                    });
                    this.ensureBucketExists();
                }
                else {
                    console.warn('Minio configuration not found. Logs will only be stored locally and in console.');
                }
            }
            else {
                // Development-specific Minio configuration
                this.minioClient = new Minio.Client({
                    endPoint: 'localhost',
                    port: 9000,
                    useSSL: false,
                    accessKey: 'serpentrace',
                    secretKey: 'serpentrace123!'
                });
                this.ensureBucketExists();
            }
        }
        catch (error) {
            console.error('Failed to initialize Minio client:', error);
            this.minioClient = null;
        }
    }
    async ensureBucketExists() {
        if (!this.minioClient)
            return;
        try {
            const exists = await this.minioClient.bucketExists(this.bucketName);
            if (!exists) {
                await this.minioClient.makeBucket(this.bucketName);
                this.log(LogLevel.STARTUP, `Created Minio bucket: ${this.bucketName}`);
            }
        }
        catch (error) {
            console.error('Failed to ensure bucket exists:', error);
        }
    }
    startPeriodicUpload() {
        // Upload current log file to Minio every 2 minutes
        this.uploadInterval = setInterval(async () => {
            if (this.currentLogFile && this.minioClient) {
                await this.uploadToMinio(this.currentLogFile);
            }
        }, 2 * 60 * 1000); // 2 minutes
    }
    getMonthlyDirectory() {
        const now = new Date();
        const year = now.getFullYear();
        const month = String(now.getMonth() + 1).padStart(2, '0');
        return path_1.default.join(this.logsDir, `${year}-${month}`);
    }
    getMonthlyMinioPrefix() {
        const now = new Date();
        const year = now.getFullYear();
        const month = String(now.getMonth() + 1).padStart(2, '0');
        return `${year}-${month}/`;
    }
    createNewLogFile() {
        const now = new Date();
        const timestamp = now.toISOString().replace(/[:.]/g, '-');
        const fileName = `serpentrace-${timestamp}.log`;
        this.currentLogFile = path_1.default.join(this.getMonthlyDirectory(), fileName);
        this.logCount = 0;
        // Write log file header
        const header = `# SerpentRace Backend Logs\n# Started: ${now.toISOString()}\n# Max entries per file: ${this.maxLogsPerFile}\n\n`;
        try {
            fs_1.default.writeFileSync(this.currentLogFile, header);
        }
        catch (error) {
            console.error('Failed to create log file:', error);
        }
    }
    formatLogEntry(entry) {
        const parts = [
            entry.timestamp,
            `[${entry.level}]`,
            entry.message
        ];
        if (entry.requestId)
            parts.push(`ReqId:${entry.requestId}`);
        if (entry.userId)
            parts.push(`UserId:${entry.userId}`);
        if (entry.ip)
            parts.push(`IP:${entry.ip}`);
        if (entry.method && entry.url)
            parts.push(`${entry.method} ${entry.url}`);
        if (entry.statusCode)
            parts.push(`Status:${entry.statusCode}`);
        if (entry.responseTime)
            parts.push(`Time:${entry.responseTime}ms`);
        if (entry.userAgent)
            parts.push(`UA:${entry.userAgent.substring(0, 50)}`);
        if (entry.metadata)
            parts.push(`Meta:${JSON.stringify(entry.metadata)}`);
        return parts.join(' | ');
    }
    async writeToLocalFile(entry) {
        if (!this.currentLogFile)
            return;
        try {
            const logLine = this.formatLogEntry(entry) + '\n';
            fs_1.default.appendFileSync(this.currentLogFile, logLine);
            this.logCount++;
            // Check if we need to rotate the log file
            if (this.logCount >= this.maxLogsPerFile) {
                await this.rotateLogFile();
            }
        }
        catch (error) {
            console.error('Failed to write to log file:', error);
        }
    }
    async rotateLogFile() {
        if (!this.currentLogFile)
            return;
        try {
            // Upload current file to Minio before rotating
            await this.uploadToMinio(this.currentLogFile);
            // Create new log file
            this.createNewLogFile();
            this.log(LogLevel.OTHER, 'Log file rotated due to size limit');
        }
        catch (error) {
            console.error('Failed to rotate log file:', error);
        }
    }
    async uploadToMinio(filePath) {
        if (!this.minioClient) {
            console.warn('Minio client not initialized, skipping upload');
            return;
        }
        if (!fs_1.default.existsSync(filePath)) {
            console.warn(`Log file does not exist: ${filePath}`);
            return;
        }
        try {
            const fileName = path_1.default.basename(filePath);
            const objectName = this.getMonthlyMinioPrefix() + fileName;
            console.log(`Attempting to upload log file to Minio: ${objectName}`);
            await this.minioClient.fPutObject(this.bucketName, objectName, filePath);
            console.log(`Successfully uploaded log file to Minio: ${objectName}`);
        }
        catch (error) {
            console.error('Failed to upload to Minio:', error);
            console.error('Minio config:', {
                endpoint: this.minioClient ? 'configured' : 'not configured',
                bucket: this.bucketName
            });
        }
    }
    logToConsole(entry) {
        const formattedEntry = this.formatLogEntry(entry);
        switch (entry.level) {
            case LogLevel.ERROR:
                console.error(formattedEntry);
                break;
            case LogLevel.WARNING:
                console.warn(formattedEntry);
                break;
            case LogLevel.REQUEST:
            case LogLevel.AUTH:
            case LogLevel.DATABASE:
            case LogLevel.CONNECTION:
                console.info(formattedEntry);
                break;
            case LogLevel.STARTUP:
                console.log(formattedEntry);
                break;
            default:
                console.log(formattedEntry);
        }
    }
    log(level, message, metadata, req, res, responseTime) {
        const entry = {
            timestamp: new Date().toISOString(),
            level,
            message,
            metadata
        };
        // Add request context if available
        if (req) {
            entry.requestId = req.requestId || this.generateRequestId();
            entry.userId = req.user?.userId;
            entry.ip = req.ip || req.socket?.remoteAddress || 'unknown';
            entry.userAgent = req.get ? req.get('User-Agent') : 'unknown';
            entry.method = req.method;
            entry.url = req.originalUrl || req.url;
        }
        if (res) {
            entry.statusCode = res.statusCode;
        }
        if (responseTime !== undefined) {
            entry.responseTime = responseTime;
        }
        // Log to all three destinations
        this.logToConsole(entry);
        this.writeToLocalFile(entry);
        // Add to buffer for potential batch processing
        this.logBuffer.push(entry);
        // Limit buffer size
        if (this.logBuffer.length > 1000) {
            this.logBuffer = this.logBuffer.slice(-500);
        }
    }
    generateRequestId() {
        return Math.random().toString(36).substr(2, 9);
    }
    async shutdown() {
        try {
            // Clear the upload interval
            if (this.uploadInterval) {
                clearInterval(this.uploadInterval);
                this.uploadInterval = null;
            }
            // Upload current log file to Minio
            if (this.currentLogFile) {
                await this.uploadToMinio(this.currentLogFile);
            }
            this.log(LogLevel.STARTUP, 'Logging service shutting down gracefully');
            // Give time for final logs to be written
            await new Promise(resolve => setTimeout(resolve, 1000));
        }
        catch (error) {
            console.error('Error during logging service shutdown:', error);
        }
    }
    // Middleware factory methods
    requestLoggingMiddleware() {
        return (req, res, next) => {
            const startTime = Date.now();
            // Generate request ID
            req.requestId = this.generateRequestId();
            // Log request start
            this.log(LogLevel.REQUEST, `Incoming request`, undefined, req);
            // Override res.end to log response
            const originalEnd = res.end.bind(res);
            res.end = (...args) => {
                const responseTime = Date.now() - startTime;
                LoggingService.getInstance().log(LogLevel.REQUEST, `Request completed`, undefined, req, res, responseTime);
                return originalEnd(...args);
            };
            next();
        };
    }
    errorLoggingMiddleware() {
        return (error, req, res, next) => {
            this.log(LogLevel.ERROR, `Unhandled error: ${error.message}`, {
                stack: error.stack,
                name: error.name
            }, req, res);
            next(error);
        };
    }
}
exports.LoggingService = LoggingService;
exports.default = LoggingService;
//# sourceMappingURL=LoggingService.js.map