const net = require('net');
const dgram = require('dgram');
const geoip = require('geoip-lite');
const crypto = require('crypto');
const chalk = require('chalk');

class AntiDDoS {
    constructor(port = 443) {
        this.port = port;
        this.tcpConnections = new Map();
        this.synFloodProtection = new Map();
        this.synAckChallenges = new Map();
        this.maxConnectionsPerMinute = 100; // Increased limit for legitimate traffic
        this.maxConcurrentConnections = 1000; // Limit on concurrent connections
        this.synAckChallengeTimeout = 5000;
        this.blacklist = new Map(); // Use Map to store blacklist with expiration
        this.geoBlockedCountries = ['ID'];
        this.packetInspection = true;
        this.challengeCollapsing = true; // Enable Proof of Work challenges
        this.powDifficulty = 3; // Difficulty for PoW (adjustable)
        this.stats = {
            tcpConnections: 0,
            blockedConnections: 0,
            rateLimitedConnections: 0,
            geoBlockedConnections: 0,
            synFloodBlocked: 0,
            challengeFailed: 0,
            powChallenged: 0,
            blacklistSize: 0,
        };
        this.globalConnectionCount = 0;
        this.connectionTimeout = 30000; // 30 seconds
    }

    log(message, type = 'info') {
        const logTypes = {
            info: chalk.blue,
            success: chalk.green,
            warning: chalk.yellow,
            error: chalk.red,
        };
        console.log(logTypes[type](message));
    }

    isGeoBlocked(ip) {
        const geo = geoip.lookup(ip);
        if (geo && this.geoBlockedCountries.includes(geo.country)) {
            this.log(`Geo-blocked connection from ${ip} (${geo.country})`, 'warning');
            return true;
        }
        return false;
    }

    throttleConnections(ip) {
        const now = Date.now();
        const timestamps = this.tcpConnections.get(ip) || [];
        timestamps.push(now);

        // Remove timestamps older than 1 minute
        while (timestamps.length && now - timestamps[0] > 60000) {
            timestamps.shift();
        }

        if (timestamps.length > this.maxConnectionsPerMinute) {
            this.log(`Rate limit exceeded by ${ip}`, 'error');
            this.addToBlacklist(ip, 60000); // Block for 1 minute
            this.stats.rateLimitedConnections++;
            return true;
        }

        this.tcpConnections.set(ip, timestamps);
        return false;
    }

    handleSYNFlood(ip) {
        const now = Date.now();
        const synAttempts = this.synFloodProtection.get(ip) || [];
        synAttempts.push(now);

        // Remove attempts older than 10 seconds
        while (synAttempts.length && now - synAttempts[0] > 10000) {
            synAttempts.shift();
        }

        if (synAttempts.length > 20) { // Increased threshold for legitimate traffic
            this.log(`SYN flood detected from ${ip}`, 'error');
            this.addToBlacklist(ip, 60000); // Block for 1 minute
            this.stats.synFloodBlocked++;
            return true;
        }

        this.synFloodProtection.set(ip, synAttempts);
        return false;
    }

    addToBlacklist(ip, duration) {
        const expiry = Date.now() + duration;
        this.blacklist.set(ip, expiry);
        this.stats.blacklistSize = this.blacklist.size;

        // Schedule removal from blacklist
        setTimeout(() => {
            this.blacklist.delete(ip);
            this.stats.blacklistSize = this.blacklist.size;
            this.log(`IP ${ip} removed from blacklist`, 'info');
        }, duration);
    }

    generateSYNACKChallenge(ip) {
        const challenge = crypto.randomBytes(32).toString('hex');
        this.synAckChallenges.set(ip, challenge);
        return challenge;
    }

    verifySYNACKChallenge(ip, response) {
        const expectedChallenge = this.synAckChallenges.get(ip);
        if (expectedChallenge === response) {
            this.synAckChallenges.delete(ip);
            return true;
        }
        this.synAckChallenges.delete(ip); // Challenge failed, remove entry
        this.log(`SYN-ACK challenge failed for IP: ${ip}`, 'error');
        this.addToBlacklist(ip, 60000); // Block for 1 minute
        this.stats.challengeFailed++;
        return false;
    }

    // Simple Proof of Work challenge (client must solve a hash puzzle)
    generatePoWChallenge() {
        const challenge = crypto.randomBytes(32).toString('hex');
        return challenge;
    }

    verifyPoWChallenge(challenge, response, difficulty = this.powDifficulty) {
        const hash = crypto.createHash('sha256').update(response).digest('hex');
        return hash.startsWith('0'.repeat(difficulty));
    }

    startTCPServer() {
        const tcpServer = net.createServer((socket) => {
            const remoteAddress = socket.remoteAddress;

            // Check if IP is blacklisted
            if (this.blacklist.has(remoteAddress)) {
                this.log(`Blocked connection attempt from blacklisted IP: ${remoteAddress}`, 'error');
                socket.destroy();
                this.stats.blockedConnections++;
                return;
            }

            // Geo-blocking
            if (this.isGeoBlocked(remoteAddress)) {
                socket.destroy();
                this.stats.geoBlockedConnections++;
                return;
            }

            // Rate limiting
            if (this.throttleConnections(remoteAddress)) {
                socket.destroy();
                return;
            }

            // SYN Flood protection
            if (this.handleSYNFlood(remoteAddress)) {
                socket.destroy();
                return;
            }

            // Global connection limit
            if (this.globalConnectionCount >= this.maxConcurrentConnections) {
                this.log(`Global connection limit reached. Blocking ${remoteAddress}`, 'error');
                this.addToBlacklist(remoteAddress, 60000);
                this.stats.blockedConnections++;
                socket.destroy();
                return;
            }

            this.globalConnectionCount++;
            this.stats.tcpConnections++;

            // Initiate SYN-ACK challenge
            const synAckChallenge = this.generateSYNACKChallenge(remoteAddress);
            socket.write(`SYN-ACK Challenge: ${synAckChallenge}\n`);

            this.log(`Sent SYN-ACK challenge to ${remoteAddress}`, 'info');

            // Set connection timeout
            socket.setTimeout(this.connectionTimeout, () => {
                this.log(`Connection timeout for IP: ${remoteAddress}`, 'warning');
                socket.destroy();
                this.synAckChallenges.delete(remoteAddress);
                this.globalConnectionCount--;
            });

            // Handle incoming data
            socket.on('data', (data) => {
                const message = data.toString().trim();

                // Check for PoW challenge if enabled
                if (this.challengeCollapsing && this.synAckChallenges.has(remoteAddress)) {
                    const powChallenge = this.generatePoWChallenge();
                    socket.write(`PoW Challenge: ${powChallenge}\n`);
                    this.log(`Sent PoW challenge to ${remoteAddress}: ${powChallenge}`, 'info');

                    socket.once('data', (powResponse) => {
                        const powResponseStr = powResponse.toString().trim();
                        if (!this.verifyPoWChallenge(powChallenge, powResponseStr)) {
                            this.log(`PoW challenge failed for IP: ${remoteAddress}`, 'error');
                            this.addToBlacklist(remoteAddress, 60000);
                            this.stats.powChallenged++;
                            socket.destroy();
                            this.globalConnectionCount--;
                            return;
                        } else {
                            this.log(`PoW challenge passed for IP: ${remoteAddress}`, 'success');
                        }
                    });
                }

                // Verify SYN-ACK challenge
                if (message === synAckChallenge) {
                    if (this.verifySYNACKChallenge(remoteAddress, message)) {
                        this.log(`SYN-ACK challenge passed for IP: ${remoteAddress}`, 'success');
                        socket.write('Welcome! Connection established.\n');
                    }
                } else {
                    // Inspect TCP flags for abnormal patterns
                    const flags = this.inspectTCPFlags(data);
                    if (flags) {
                        this.log(`Abnormal TCP flags from ${remoteAddress}: ${flags}`, 'warning');
                        this.addToBlacklist(remoteAddress, 60000);
                        this.stats.blockedConnections++;
                        socket.destroy();
                        this.globalConnectionCount--;
                        return;
                    }

                    this.log(`Received TCP data from ${remoteAddress}: ${message}`, 'info');
                }
            });

            socket.on('close', () => {
                this.log(`TCP connection closed: ${remoteAddress}`, 'info');
                this.tcpConnections.delete(remoteAddress);
                this.synAckChallenges.delete(remoteAddress);
                this.globalConnectionCount--;
            });

            socket.on('error', (err) => {
                this.log(`TCP connection error from ${remoteAddress}: ${err.message}`, 'error');
                this.synAckChallenges.delete(remoteAddress);
                this.globalConnectionCount--;
            });

            // Track connection timestamps for rate limiting
            const timestamps = this.tcpConnections.get(remoteAddress) || [];
            timestamps.push(Date.now());
            this.tcpConnections.set(remoteAddress, timestamps);
            this.log(`TCP connection established: ${remoteAddress}`, 'success');
        });

        tcpServer.on('error', (err) => {
            this.log(`TCP server error: ${err.message}`, 'error');
        });

        tcpServer.listen(this.port, () => {
            this.log(`TCP server running on port ${this.port}`, 'success');
        });
    }

    inspectTCPFlags(data) {
        // Placeholder for TCP flag inspection
        // In a real-world scenario, you'd parse the TCP headers to inspect flags
        // For simplicity, we'll assume that certain strings indicate abnormal flags
        const message = data.toString().toUpperCase();
        if (message.includes('RST') || message.includes('FIN') || message.includes('URG')) {
            return message.match(/RST|FIN|URG/g).join(', ');
        }
        return null;
    }

    startUDPServer() {
        const udpServer = dgram.createSocket('udp4');

        udpServer.on('message', (msg, rinfo) => {
            const remoteAddress = rinfo.address;

            if (this.blacklist.has(remoteAddress)) {
                this.log(`UDP message blocked from blacklisted IP: ${remoteAddress}`, 'error');
                return;
            }

            if (this.isGeoBlocked(remoteAddress)) {
                this.stats.geoBlockedConnections++;
                return;
            }

            this.log(`Received UDP message from ${remoteAddress}: ${msg.toString()}`, 'info');
        });

        udpServer.on('error', (err) => {
            this.log(`UDP server error: ${err.message}`, 'error');
            udpServer.close();
        });

        udpServer.bind(this.port, () => {
            this.log(`UDP server running on port ${this.port}`, 'success');
        });
    }

    getStats() {
        return this.stats;
    }

    startMonitoring() {
        setInterval(() => {
            console.log('Current stats:', this.getStats());
        }, 10000);
    }
}

// Example usage
const antiDDoS = new AntiDDoS(443);
antiDDoS.startTCPServer();
antiDDoS.startUDPServer();
antiDDoS.startMonitoring();


make block all countries except INDONESIA