import { createLogger } from '@4runr/shared';
import crypto from 'crypto';
const logger = createLogger('Gateway:DDOS:Protection');
const requestFingerprints = new Map();
const ipBlocks = new Map();
const rateLimitBuckets = new Map();
const CONFIG = {
    MAX_REQUESTS_PER_MINUTE: 100,
    MAX_REQUESTS_PER_SECOND: 20,
    MAX_CONCURRENT_REQUESTS: 50,
    BLOCK_THRESHOLD_REQUESTS: 200,
    BLOCK_DURATION_MS: 15 * 60 * 1000,
    BLOCK_THRESHOLD_FAILURES: 10,
    CLEANUP_INTERVAL_MS: 60 * 1000,
    FINGERPRINT_TTL_MS: 60 * 60 * 1000,
};
function generateFingerprint(request) {
    const ip = getClientIP(request);
    const userAgent = request.headers['user-agent'] || 'unknown';
    const accept = request.headers['accept'] || '';
    const acceptLanguage = request.headers['accept-language'] || '';
    const data = `${ip}:${userAgent}:${accept}:${acceptLanguage}`;
    return crypto.createHash('sha256').update(data).digest('hex').substring(0, 16);
}
function isLoopbackIP(ip) {
    const n = ip.toLowerCase();
    return (n === '127.0.0.1' ||
        n === '::1' ||
        n === '::ffff:127.0.0.1' ||
        n === 'localhost');
}
function loopbackExemptFromDdos() {
    if (process.env['DDOS_LOOPBACK_EXEMPT'] === 'false')
        return false;
    return process.env['NODE_ENV'] !== 'production';
}
function getClientIP(request) {
    const forwarded = request.headers['x-forwarded-for'];
    if (forwarded) {
        const forwardedStr = Array.isArray(forwarded) ? forwarded[0] : forwarded;
        if (forwardedStr) {
            const ips = forwardedStr.split(',');
            return ips[0]?.trim() || 'unknown';
        }
    }
    const realIp = request.headers['x-real-ip'];
    const realIpStr = Array.isArray(realIp) ? realIp[0] : realIp;
    return request.ip || realIpStr || 'unknown';
}
function isIPBlocked(ip) {
    const block = ipBlocks.get(ip);
    if (!block)
        return false;
    if (Date.now() > block.blockedUntil) {
        ipBlocks.delete(ip);
        return false;
    }
    return true;
}
function blockIP(ip, reason, requestCount = 0) {
    const blockedUntil = Date.now() + CONFIG.BLOCK_DURATION_MS;
    ipBlocks.set(ip, {
        ip,
        blockedAt: Date.now(),
        blockedUntil,
        reason,
        requestCount
    });
    logger.warn('IP blocked', { ip, reason, blockedUntil: new Date(blockedUntil) });
}
function checkRateLimit(fingerprint, ip) {
    const now = Date.now();
    const minuteKey = `minute:${fingerprint}:${Math.floor(now / 60000)}`;
    const secondKey = `second:${fingerprint}:${Math.floor(now / 1000)}`;
    let minuteBucket = rateLimitBuckets.get(minuteKey);
    if (!minuteBucket || now >= minuteBucket.resetAt) {
        minuteBucket = { count: 0, resetAt: now + 60000 };
        rateLimitBuckets.set(minuteKey, minuteBucket);
    }
    minuteBucket.count++;
    if (minuteBucket.count > CONFIG.MAX_REQUESTS_PER_MINUTE) {
        blockIP(ip, 'rate_limit_exceeded', minuteBucket.count);
        return { allowed: false, reason: 'Rate limit exceeded (per minute)' };
    }
    let secondBucket = rateLimitBuckets.get(secondKey);
    if (!secondBucket || now >= secondBucket.resetAt) {
        secondBucket = { count: 0, resetAt: now + 1000 };
        rateLimitBuckets.set(secondKey, secondBucket);
    }
    secondBucket.count++;
    if (secondBucket.count > CONFIG.MAX_REQUESTS_PER_SECOND) {
        blockIP(ip, 'rate_limit_exceeded', secondBucket.count);
        return { allowed: false, reason: 'Rate limit exceeded (per second)' };
    }
    return { allowed: true };
}
function trackFingerprint(fingerprint, ip, userAgent) {
    const now = Date.now();
    const existing = requestFingerprints.get(fingerprint);
    if (existing) {
        existing.count++;
        existing.lastSeen = now;
        if (existing.count > CONFIG.BLOCK_THRESHOLD_REQUESTS) {
            if (!existing.blocked) {
                existing.blocked = true;
                existing.blockedUntil = now + CONFIG.BLOCK_DURATION_MS;
                blockIP(ip, 'suspicious_activity', existing.count);
            }
        }
    }
    else {
        requestFingerprints.set(fingerprint, {
            ip,
            userAgent,
            fingerprint,
            count: 1,
            firstSeen: now,
            lastSeen: now,
            blocked: false
        });
    }
}
function cleanup() {
    const now = Date.now();
    for (const [key, fp] of requestFingerprints.entries()) {
        if (now - fp.lastSeen > CONFIG.FINGERPRINT_TTL_MS) {
            requestFingerprints.delete(key);
        }
    }
    for (const [ip, block] of ipBlocks.entries()) {
        if (now > block.blockedUntil) {
            ipBlocks.delete(ip);
        }
    }
    for (const [key, bucket] of rateLimitBuckets.entries()) {
        if (now >= bucket.resetAt) {
            rateLimitBuckets.delete(key);
        }
    }
}
setInterval(cleanup, CONFIG.CLEANUP_INTERVAL_MS);
function isProbeExemptPath(method, rawUrl) {
    if (method !== 'GET')
        return false;
    const path = (rawUrl.split('?')[0] || '').toLowerCase();
    return path === '/health' || path === '/ready' || path === '/metrics';
}
export async function ddosProtection(request, reply) {
    if (isProbeExemptPath(request.method, request.url)) {
        return;
    }
    const ip = getClientIP(request);
    if (loopbackExemptFromDdos() && isLoopbackIP(ip)) {
        return;
    }
    const userAgent = request.headers['user-agent'] || 'unknown';
    if (isIPBlocked(ip)) {
        const block = ipBlocks.get(ip);
        const retryAfter = Math.ceil((block.blockedUntil - Date.now()) / 1000);
        reply.status(429).send({
            error: 'ip_blocked',
            message: 'IP address is temporarily blocked',
            reason: block.reason,
            retry_after: retryAfter,
            blocked_until: new Date(block.blockedUntil).toISOString()
        });
        return;
    }
    const fingerprint = generateFingerprint(request);
    const rateLimitCheck = checkRateLimit(fingerprint, ip);
    if (!rateLimitCheck.allowed) {
        reply.status(429).send({
            error: 'rate_limited',
            message: rateLimitCheck.reason || 'Too many requests'
        });
        return;
    }
    trackFingerprint(fingerprint, ip, userAgent);
    const fp = requestFingerprints.get(fingerprint);
    if (fp?.blocked && fp.blockedUntil && Date.now() < fp.blockedUntil) {
        reply.status(429).send({
            error: 'suspicious_activity',
            message: 'Request pattern flagged as suspicious'
        });
        return;
    }
    request.ddosFingerprint = fingerprint;
    request.clientIP = ip;
}
export function getDDoSStats() {
    return {
        blockedIPs: ipBlocks.size,
        trackedFingerprints: requestFingerprints.size,
        activeRateLimitBuckets: rateLimitBuckets.size,
        blockedIPsList: Array.from(ipBlocks.values()).map(b => ({
            ip: b.ip,
            reason: b.reason,
            blockedUntil: new Date(b.blockedUntil).toISOString()
        }))
    };
}
//# sourceMappingURL=ddos-protection.js.map