const UnitOfWork = require('../database/UnitOfWork');
const RateLimitRepository = require('../repositories/rateLimit/RateLimitRepository');

/**
 * Factory for rate limiter middleware.
 * @param {Object} options
 * @param {function(req): string} options.keyGenerator - Function to generate a unique key per request.
 * @param {number} options.max - Max allowed requests per window.
 * @param {number} options.windowSeconds - Window size in seconds.
 * @returns {function} Express middleware
 */
function createRateLimiter({ keyGenerator, max, windowSeconds }) {
    return async function rateLimiter(req, res, next) {
        const rateKey = keyGenerator(req);
        const now = new Date();
        const windowStart = new Date(Math.floor(now.getTime() / (windowSeconds * 1000)) * windowSeconds * 1000);

        const uow = new UnitOfWork();
        try {
            await uow.start();
            const repo = new RateLimitRepository(uow.connection);

            // Cleanup old rows with 1% probability
            if (Math.random() < 0.01) {
                await repo.cleanupOldRows(30); // 30 days retention
            }

            // Atomically increment or create
            const row = await repo.incrementOrCreate(rateKey, windowStart, windowSeconds, max, 1);

            if (row.count > max) {
                await uow.rollback();
                // Custom JSON error response
                return res.status(429).json({
                    success: false,
                    message: 'Rate limit exceeded. Please try again later.'
                });
            }

            await uow.commit();
            next();
        } catch (err) {
            await uow.rollback(err);
            return res.status(500).json({
                success: false,
                message: 'Internal server error (rate limiter)'
            });
        }
    };
}

/**
 * Checks and/or increments the rate limit for a given key.
 * If res is null, only checks (does not increment or send response).
 * If res is provided, increments and sends response if limited.
 * @param {Object} options
 * @param {string} rateKey
 * @param {number} max
 * @param {number} windowSeconds
 * @param {Object|null} res - Express response object or null
 * @returns {Promise<boolean>} true if limited, false otherwise
 */
async function checkAndIncrementRateLimit({ rateKey, max, windowSeconds }, res) {
    const now = new Date();
    const windowStart = new Date(Math.floor(now.getTime() / (windowSeconds * 1000)) * windowSeconds * 1000);

    const uow = new UnitOfWork();
    try {
        await uow.start();
        const repo = new RateLimitRepository(uow.connection);

        // Cleanup old rows with 1% probability
        if (Math.random() < 0.01) {
            await repo.cleanupOldRows(30); // 30 days retention
        }

        let row;
        if (res === null) {
            // "Check only" mode: do NOT increment, just check
            row = await repo.getForUpdate(rateKey, windowStart);
            if (row && row.count >= max) {
                await uow.rollback();
                console.warn(`[RATE LIMIT] Transaction rollback: rate limit exceeded for key ${rateKey} at ${new Date().toISOString()}`);
                return true;
            }
            await uow.commit();
            return false;
        } else {
            // "Increment" mode: increment or create
            row = await repo.incrementOrCreate(rateKey, windowStart, windowSeconds, max, 1);
            if (row.count > max) {
                await uow.rollback();
                console.warn(`[RATE LIMIT] Transaction rollback: rate limit exceeded for key ${rateKey} at ${new Date().toISOString()}`);
                res.status(429).json({
                    success: false,
                    message: 'Rate limit exceeded. Please try again later.'
                });
                return true;
            }
            await uow.commit();
            return false;
        }
    } catch (err) {
        await uow.rollback(err);
        if (res) {
            console.error(`[RATE LIMIT] Transaction rollback: internal error for key ${rateKey} at ${new Date().toISOString()}`);
            res.status(500).json({
                success: false,
                message: 'Internal server error (rate limiter)'
            });
        }
        return true;
    }
}

module.exports = { createRateLimiter, checkAndIncrementRateLimit };
