import * as plugins from '../plugins.js';
import { logger } from '../logger.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
import { LRUCache } from 'lru-cache';
import { CachedIPReputation } from '../db/documents/classes.cached.ip.reputation.js';
/**
 * Reputation threshold scores
 */
export var ReputationThreshold;
(function (ReputationThreshold) {
    ReputationThreshold[ReputationThreshold["HIGH_RISK"] = 20] = "HIGH_RISK";
    ReputationThreshold[ReputationThreshold["MEDIUM_RISK"] = 50] = "MEDIUM_RISK";
    ReputationThreshold[ReputationThreshold["LOW_RISK"] = 80] = "LOW_RISK"; // Score below this is considered low risk (but not trusted)
})(ReputationThreshold || (ReputationThreshold = {}));
/**
 * IP type classifications
 */
export var IPType;
(function (IPType) {
    IPType["RESIDENTIAL"] = "residential";
    IPType["DATACENTER"] = "datacenter";
    IPType["PROXY"] = "proxy";
    IPType["TOR"] = "tor";
    IPType["VPN"] = "vpn";
    IPType["UNKNOWN"] = "unknown";
})(IPType || (IPType = {}));
/**
 * Class for checking IP reputation of inbound email senders
 */
export class IPReputationChecker {
    static instance;
    reputationCache;
    options;
    // Default DNSBL servers
    static DEFAULT_DNSBL_SERVERS = [
        'zen.spamhaus.org', // Spamhaus
        'bl.spamcop.net', // SpamCop
        'b.barracudacentral.org', // Barracuda
        'spam.dnsbl.sorbs.net', // SORBS
        'dnsbl.sorbs.net', // SORBS (expanded)
        'cbl.abuseat.org', // Composite Blocking List
        'xbl.spamhaus.org', // Spamhaus XBL
        'pbl.spamhaus.org', // Spamhaus PBL
        'dnsbl-1.uceprotect.net', // UCEPROTECT
        'psbl.surriel.com' // PSBL
    ];
    // Default options
    static DEFAULT_OPTIONS = {
        maxCacheSize: 10000,
        cacheTTL: 24 * 60 * 60 * 1000, // 24 hours
        dnsblServers: IPReputationChecker.DEFAULT_DNSBL_SERVERS,
        highRiskThreshold: ReputationThreshold.HIGH_RISK,
        mediumRiskThreshold: ReputationThreshold.MEDIUM_RISK,
        lowRiskThreshold: ReputationThreshold.LOW_RISK,
        enableLocalCache: true,
        enableDNSBL: true,
        enableIPInfo: true
    };
    /**
     * Constructor for IPReputationChecker
     * @param options Configuration options
     */
    constructor(options = {}) {
        // Merge with default options
        this.options = {
            ...IPReputationChecker.DEFAULT_OPTIONS,
            ...options
        };
        // Initialize reputation cache
        this.reputationCache = new LRUCache({
            max: this.options.maxCacheSize,
            ttl: this.options.cacheTTL, // Cache TTL
        });
        // Load persisted reputations into in-memory cache
        if (this.options.enableLocalCache) {
            this.loadCacheFromDb().catch((error) => {
                logger.log('error', `Failed to load IP reputation cache during initialization: ${error.message}`);
            });
        }
    }
    /**
     * Get the singleton instance of the checker
     * @param options Configuration options
     * @returns Singleton instance
     */
    static getInstance(options = {}) {
        if (!IPReputationChecker.instance) {
            IPReputationChecker.instance = new IPReputationChecker(options);
        }
        return IPReputationChecker.instance;
    }
    /**
     * Reset the singleton instance (for shutdown/testing)
     */
    static resetInstance() {
        IPReputationChecker.instance = undefined;
    }
    /**
     * Check an IP address's reputation
     * @param ip IP address to check
     * @returns Reputation check result
     */
    async checkReputation(ip) {
        try {
            // Validate IP address format
            if (!this.isValidIPAddress(ip)) {
                logger.log('warn', `Invalid IP address format: ${ip}`);
                return this.createErrorResult(ip, 'Invalid IP address format');
            }
            // Check in-memory LRU cache first (fast path)
            const cachedResult = this.reputationCache.get(ip);
            if (cachedResult) {
                logger.log('info', `Using cached reputation data for IP ${ip}`, {
                    score: cachedResult.score,
                    isSpam: cachedResult.isSpam
                });
                return cachedResult;
            }
            // Initialize empty result
            const result = {
                score: 100, // Start with perfect score
                isSpam: false,
                isProxy: false,
                isTor: false,
                isVPN: false,
                timestamp: Date.now()
            };
            // Check IP against DNS blacklists if enabled
            if (this.options.enableDNSBL) {
                const dnsblResult = await this.checkDNSBL(ip);
                // Update result with DNSBL information
                result.score -= dnsblResult.listCount * 10; // Subtract 10 points per blacklist
                result.isSpam = dnsblResult.listCount > 0;
                result.blacklists = dnsblResult.lists;
            }
            // Get additional IP information if enabled
            if (this.options.enableIPInfo) {
                const ipInfo = await this.getIPInfo(ip);
                // Update result with IP info
                result.country = ipInfo.country;
                result.asn = ipInfo.asn;
                result.org = ipInfo.org;
                // Adjust score based on IP type
                if (ipInfo.type === IPType.PROXY || ipInfo.type === IPType.TOR || ipInfo.type === IPType.VPN) {
                    result.score -= 30; // Subtract 30 points for proxies, Tor, VPNs
                    // Set proxy flags
                    result.isProxy = ipInfo.type === IPType.PROXY;
                    result.isTor = ipInfo.type === IPType.TOR;
                    result.isVPN = ipInfo.type === IPType.VPN;
                }
            }
            // Ensure score is between 0 and 100
            result.score = Math.max(0, Math.min(100, result.score));
            // Update in-memory LRU cache
            this.reputationCache.set(ip, result);
            // Persist to database if enabled (fire and forget)
            if (this.options.enableLocalCache) {
                this.persistReputationToDb(ip, result).catch((error) => {
                    logger.log('error', `Failed to persist IP reputation for ${ip}: ${error.message}`);
                });
            }
            // Log the reputation check
            this.logReputationCheck(ip, result);
            return result;
        }
        catch (error) {
            logger.log('error', `Error checking IP reputation for ${ip}: ${error.message}`, {
                ip,
                stack: error.stack
            });
            return this.createErrorResult(ip, error.message);
        }
    }
    /**
     * Check an IP against DNS blacklists
     * @param ip IP address to check
     * @returns DNSBL check results
     */
    async checkDNSBL(ip) {
        try {
            // Reverse the IP for DNSBL queries
            const reversedIP = this.reverseIP(ip);
            const results = await Promise.allSettled(this.options.dnsblServers.map(async (server) => {
                try {
                    const lookupDomain = `${reversedIP}.${server}`;
                    await plugins.dns.promises.resolve(lookupDomain);
                    return server; // IP is listed in this DNSBL
                }
                catch (error) {
                    if (error.code === 'ENOTFOUND') {
                        return null; // IP is not listed in this DNSBL
                    }
                    throw error; // Other error
                }
            }));
            // Extract successful lookups (listed in DNSBL)
            const lists = results
                .filter((result) => result.status === 'fulfilled' && result.value !== null)
                .map(result => result.value);
            return {
                listCount: lists.length,
                lists
            };
        }
        catch (error) {
            logger.log('error', `Error checking DNSBL for ${ip}: ${error.message}`);
            return {
                listCount: 0,
                lists: []
            };
        }
    }
    /**
     * Get information about an IP address
     * @param ip IP address to check
     * @returns IP information
     */
    async getIPInfo(ip) {
        try {
            // In a real implementation, this would use an IP data service API
            // For this implementation, we'll use a simplified approach
            // Check if it's a known Tor exit node (simplified)
            const isTor = ip.startsWith('171.25.') || ip.startsWith('185.220.') || ip.startsWith('95.216.');
            // Check if it's a known VPN (simplified)
            const isVPN = ip.startsWith('185.156.') || ip.startsWith('37.120.');
            // Check if it's a known proxy (simplified)
            const isProxy = ip.startsWith('34.92.') || ip.startsWith('34.206.');
            // Determine IP type
            let type = IPType.UNKNOWN;
            if (isTor) {
                type = IPType.TOR;
            }
            else if (isVPN) {
                type = IPType.VPN;
            }
            else if (isProxy) {
                type = IPType.PROXY;
            }
            else {
                // Simple datacenters detection (major cloud providers)
                if (ip.startsWith('13.') || // AWS
                    ip.startsWith('35.') || // Google Cloud
                    ip.startsWith('52.') || // AWS
                    ip.startsWith('34.') || // Google Cloud
                    ip.startsWith('104.') // Various providers
                ) {
                    type = IPType.DATACENTER;
                }
                else {
                    type = IPType.RESIDENTIAL;
                }
            }
            // Return the information
            return {
                country: this.determineCountry(ip), // Simplified, would use geolocation service
                asn: 'AS12345', // Simplified, would look up real ASN
                org: this.determineOrg(ip), // Simplified, would use real org data
                type
            };
        }
        catch (error) {
            logger.log('error', `Error getting IP info for ${ip}: ${error.message}`);
            return {
                type: IPType.UNKNOWN
            };
        }
    }
    /**
     * Simplified method to determine country from IP
     * In a real implementation, this would use a geolocation database or service
     * @param ip IP address
     * @returns Country code
     */
    determineCountry(ip) {
        // Simplified mapping for demo purposes
        if (ip.startsWith('13.') || ip.startsWith('52.'))
            return 'US';
        if (ip.startsWith('35.') || ip.startsWith('34.'))
            return 'US';
        if (ip.startsWith('185.'))
            return 'NL';
        if (ip.startsWith('171.'))
            return 'DE';
        return 'XX'; // Unknown
    }
    /**
     * Simplified method to determine organization from IP
     * In a real implementation, this would use an IP-to-org database or service
     * @param ip IP address
     * @returns Organization name
     */
    determineOrg(ip) {
        // Simplified mapping for demo purposes
        if (ip.startsWith('13.') || ip.startsWith('52.'))
            return 'Amazon AWS';
        if (ip.startsWith('35.') || ip.startsWith('34.'))
            return 'Google Cloud';
        if (ip.startsWith('185.156.'))
            return 'NordVPN';
        if (ip.startsWith('37.120.'))
            return 'ExpressVPN';
        if (ip.startsWith('185.220.'))
            return 'Tor Exit Node';
        return 'Unknown';
    }
    /**
     * Reverse an IP address for DNSBL lookups (e.g., 1.2.3.4 -> 4.3.2.1)
     * @param ip IP address to reverse
     * @returns Reversed IP for DNSBL queries
     */
    reverseIP(ip) {
        return ip.split('.').reverse().join('.');
    }
    /**
     * Create an error result for when reputation check fails
     * @param ip IP address
     * @param errorMessage Error message
     * @returns Error result
     */
    createErrorResult(ip, errorMessage) {
        return {
            score: 50, // Neutral score for errors
            isSpam: false,
            isProxy: false,
            isTor: false,
            isVPN: false,
            timestamp: Date.now(),
            error: errorMessage
        };
    }
    /**
     * Validate IP address format
     * @param ip IP address to validate
     * @returns Whether the IP is valid
     */
    isValidIPAddress(ip) {
        // IPv4 regex pattern
        const ipv4Pattern = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
        return ipv4Pattern.test(ip);
    }
    /**
     * Log reputation check to security logger
     * @param ip IP address
     * @param result Reputation result
     */
    logReputationCheck(ip, result) {
        // Determine log level based on reputation score
        let logLevel = SecurityLogLevel.INFO;
        if (result.score < this.options.highRiskThreshold) {
            logLevel = SecurityLogLevel.WARN;
        }
        else if (result.score < this.options.mediumRiskThreshold) {
            logLevel = SecurityLogLevel.INFO;
        }
        // Log the check
        SecurityLogger.getInstance().logEvent({
            level: logLevel,
            type: SecurityEventType.IP_REPUTATION,
            message: `IP reputation check ${result.isSpam ? 'flagged spam' : 'completed'} for ${ip}`,
            ipAddress: ip,
            details: {
                score: result.score,
                isSpam: result.isSpam,
                isProxy: result.isProxy,
                isTor: result.isTor,
                isVPN: result.isVPN,
                country: result.country,
                blacklists: result.blacklists
            },
            success: !result.isSpam
        });
    }
    /**
     * Persist a single IP reputation result to the database via CachedIPReputation
     */
    async persistReputationToDb(ip, result) {
        try {
            const data = {
                score: result.score,
                isSpam: result.isSpam,
                isProxy: result.isProxy,
                isTor: result.isTor,
                isVPN: result.isVPN,
                country: result.country,
                asn: result.asn,
                org: result.org,
                blacklists: result.blacklists,
            };
            const existing = await CachedIPReputation.findByIP(ip);
            if (existing) {
                existing.updateReputation(data);
                await existing.save();
            }
            else {
                const doc = CachedIPReputation.fromReputationData(ip, data);
                await doc.save();
            }
        }
        catch (error) {
            logger.log('error', `Failed to persist IP reputation for ${ip}: ${error.message}`);
        }
    }
    /**
     * Load persisted reputations from CachedIPReputation documents into the in-memory LRU cache
     */
    async loadCacheFromDb() {
        try {
            const docs = await CachedIPReputation.getInstances({});
            let loadedCount = 0;
            for (const doc of docs) {
                // Skip expired documents
                if (doc.isExpired()) {
                    continue;
                }
                const result = {
                    score: doc.score,
                    isSpam: doc.isSpam,
                    isProxy: doc.isProxy,
                    isTor: doc.isTor,
                    isVPN: doc.isVPN,
                    country: doc.country || undefined,
                    asn: doc.asn || undefined,
                    org: doc.org || undefined,
                    blacklists: doc.blacklists || [],
                    timestamp: doc.lastAccessedAt?.getTime() ?? doc.createdAt?.getTime() ?? Date.now(),
                };
                this.reputationCache.set(doc.ipAddress, result);
                loadedCount++;
            }
            if (loadedCount > 0) {
                logger.log('info', `Loaded ${loadedCount} IP reputation cache entries from database`);
            }
        }
        catch (error) {
            logger.log('error', `Failed to load IP reputation cache from database: ${error.message}`);
        }
    }
    /**
     * Get the risk level for a reputation score
     * @param score Reputation score (0-100)
     * @returns Risk level description
     */
    static getRiskLevel(score) {
        if (score < ReputationThreshold.HIGH_RISK) {
            return 'high';
        }
        else if (score < ReputationThreshold.MEDIUM_RISK) {
            return 'medium';
        }
        else if (score < ReputationThreshold.LOW_RISK) {
            return 'low';
        }
        else {
            return 'trusted';
        }
    }
}
//# sourceMappingURL=data:application/json;base64, 