<?php

class IpList {
    private $iplist = [];
    private $ipfile = "";
    private $cache = [];
    private $range = "";
    private $ipqs_api_key = 'YOUR_IPQS_API_KEY';
    public function __construct($list) {
        $this->ipfile = $list;
        $this->loadList();
    }

    public function loadList() {
        try {
            $contents = [];
            $lines = $this->read($this->ipfile);
            if ($lines) {
                foreach ($lines as $line) {
                    $line = trim($line);
                    if (empty($line) || $line[0] == '#') {
                        continue;
                    }
                    $temp = explode("#", $line);
                    $line = trim($temp[0]);
                    if ($this->validateIpOrRange($line)) {
                        $contents[] = $this->normal($line);
                    }
                }
                $this->iplist = $contents;
            }
        } catch (Exception $e) {
            error_log("Error loading IP list from {$this->ipfile}: " . $e->getMessage());
            throw $e;
        }
    }

    private function normal($range) {
        if (strpbrk("*-/", $range) === false) {
            $range .= "/32"; // Default to single IP if no range specified
        }
        return str_replace(' ', '', $range);
    }

    private function ip_in_range($ip, $range) {
        if (strpos($range, '/') !== false) {
            list($range, $netmask) = explode('/', $range, 2);
            if (strpos($netmask, '.') !== false) {
                $netmask = str_replace('*', '0', $netmask);
                $netmask_dec = ip2long($netmask);
                return ((ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec));
            } else {
                $x = explode('.', $range);
                while (count($x) < 4) $x[] = '0';
                list($a, $b, $c, $d) = $x;
                $range = sprintf("%u.%u.%u.%u", empty($a) ? '0' : $a, empty($b) ? '0' : $b, empty($c) ? '0' : $c, empty($d) ? '0' : $d);
                $range_dec = ip2long($range);
                $ip_dec = ip2long($ip);
                $wildcard_dec = pow(2, (32 - $netmask)) - 1;
                $netmask_dec = ~$wildcard_dec;
                return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec));
            }
        } else {
            if (strpos($range, '*') !== false) {
                $lower = str_replace('*', '0', $range);
                $upper = str_replace('*', '255', $range);
                $range = "$lower-$upper";
            }

            if (strpos($range, '-') !== false) {
                list($lower, $upper) = explode('-', $range, 2);
                $lower_dec = (float)sprintf("%u", ip2long($lower));
                $upper_dec = (float)sprintf("%u", ip2long($upper));
                $ip_dec = (float)sprintf("%u", ip2long($ip));
                return (($ip_dec >= $lower_dec) && ($ip_dec <= $upper_dec));
            }
            return $ip === $range; // مقارنة IP واحد
        }
    }

    private function read($fname) {
        if (!file_exists($fname)) {
            touch($fname);
            return [];
        }
        try {
            return file($fname, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        } catch (Exception $e) {
            throw new Exception("Error reading file {$fname}: " . $e->getMessage());
        }
    }

    private function validateIpOrRange($ip) {
        if (empty($ip)) return false;

        if (strpos($ip, '/') !== false) {
            list($baseIp, $cidr) = explode('/', $ip);
            return filter_var($baseIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) &&
                   is_numeric($cidr) && $cidr >= 0 && $cidr <= 32;
        }
        if (strpos($ip, '-') !== false) {
            list($start, $end) = explode('-', $ip);
            return filter_var(trim($start), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) &&
                   filter_var(trim($end), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
        }
        if (strpos($ip, '*') !== false) {
            $parts = explode('.', $ip);
            return count($parts) === 4;
        }
        return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
    }

    private function getGeoLocation($ip) {
        if (isset($this->cache[$ip]['geo']) && (time() - $this->cache[$ip]['geo_timestamp']) < 86400) {
            return $this->cache[$ip]['geo'];
        }

        if (!$this->ipqs_api_key) return [];

        $url = "https://ipqualityscore.com/api/json/ip/{$this->ipqs_api_key}/$ip?strictness=1";
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        $response = curl_exec($ch);
        $error = curl_error($ch);
        curl_close($ch);

        if ($response !== false && empty($error)) {
            $data = json_decode($response, true);
            if ($data && isset($data['success']) && $data['success']) {
                $this->cache[$ip]['geo'] = $data;
                $this->cache[$ip]['geo_timestamp'] = time();
                return $data;
            }
        }

        error_log("Failed to fetch geo data for IP $ip: " . ($error ?: 'Invalid response'));
        return [];
    }

    public function is_inlist($ip) {
        if (isset($this->cache[$ip]['inlist'])) {
            return $this->cache[$ip]['inlist'];
        }

        $geoData = $this->getGeoLocation($ip);
        $isUS = isset($geoData['country_code']) && strtoupper($geoData['country_code']) === 'US';

        $retval = false;
        foreach ($this->iplist as $ipf) {
            $retval = $this->ip_in_range($ip, $ipf);
            if ($retval === true) {
                // إذا كان المستخدم من الولايات المتحدة ولم يتم تأكيد أنه بوت، تجاهل الحظر
                if ($isUS && (!isset($geoData['bot_status']) || !$geoData['bot_status'])) {
                    $this->cache[$ip]['inlist'] = false;
                    return false;
                }
                // إذا لم يتم تحديد الموقع، لا نحظر مباشرة
                if (empty($geoData)) {
                    $this->cache[$ip]['inlist'] = false;
                    return false;
                }
                $this->range = $ipf;
                $this->cache[$ip]['inlist'] = true;
                return true;
            }
        }
        $this->cache[$ip]['inlist'] = false;
        return false;
    }

    public function updateFromUrl($url) {
        try {
            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            $data = curl_exec($ch);
            $error = curl_error($ch);
            curl_close($ch);

            if ($data === false) {
                throw new Exception("Failed to fetch data from URL: $url - $error");
            }

            $lines = explode("\n", $data);
            $newEntries = [];

            foreach ($lines as $line) {
                $line = trim($line);
                if (empty($line) || strpos($line, '#') === 0) {
                    continue;
                }

                if (preg_match('/(?:deny from|order deny,allow|deny) ([\d\.\/\-\*]+)/i', $line, $matches)) {
                    $ip = trim($matches[1]);
                } else {
                    $ip = $line;
                }

                if ($this->validateIpOrRange($ip)) {
                    $newEntries[] = $this->normal($ip);
                }
            }

            if (!empty($newEntries)) {
                $existingContent = file_exists($this->ipfile) ? file_get_contents($this->ipfile) : '';
                $existingLines = $existingContent ? explode("\n", $existingContent) : [];
                $finalEntries = array_unique(array_merge($existingLines, $newEntries));
                $finalEntries = array_filter($finalEntries, 'trim');

                $success = file_put_contents($this->ipfile, implode("\n", $finalEntries) . "\n");
                if ($success === false) {
                    throw new Exception("Failed to write to file: " . $this->ipfile);
                }

                $this->loadList();
                return count($newEntries);
            }
            return 0;
        } catch (Exception $e) {
            error_log("Error updating IP list from URL: " . $e->getMessage());
            throw $e;
        }
    }

    public function getRange() {
        return $this->range;
    }
}

class IpBlockList {
    private $statusid = ['negative' => -1, 'neutral' => 0, 'positive' => 1];
    private $whitelist;
    private $blacklist;
    private $message = null;
    private $status = null;
    private $whitelistfile;
    private $blacklistfile;

    public function __construct() {
        try {
            $dir = dirname(__FILE__);

            $this->whitelistfile = $dir . "/whitelist.dat";
            $this->blacklistfile = $dir . "/blacklist.dat";

            if (!file_exists($this->whitelistfile)) {
                touch($this->whitelistfile);
            }
            if (!file_exists($this->blacklistfile)) {
                touch($this->blacklistfile);
            }

            $this->whitelist = new IpList($this->whitelistfile);
            $this->blacklist = new IpList($this->blacklistfile);

        } catch (Exception $e) {
            error_log("Error initializing IpBlockList: " . $e->getMessage());
            throw $e;
        }
    }

    public function ipPass($ip) {
        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            throw new Exception('Requires a valid IPv4 address');
        }

        if ($this->whitelist->is_inlist($ip)) {
            $this->message = "$ip is allowed by whitelist rule: " . $this->whitelist->getRange();
            $this->status = $this->statusid['positive'];
            return true;
        }

        if ($this->blacklist->is_inlist($ip)) {
            $this->message = "$ip is blocked by blacklist rule: " . $this->blacklist->getRange();
            $this->status = $this->statusid['negative'];
            // إضافة خيار CAPTCHA بدلاً من الحظر المباشر
            header("Location: captcha_verification.php?ip=$ip&reason=Blacklist%20match");
            exit;
        }

        $this->message = "$ip is not in any list.";
        $this->status = $this->statusid['neutral'];
        return true;
    }

    public function getMessage() {
        return $this->message;
    }

    public function getStatus() {
        return $this->status;
    }

    public function updateBlacklist($sources) {
        if (!is_array($sources)) {
            $sources = [$sources];
        }

        $totalAdded = 0;
        $errors = [];

        foreach ($sources as $source) {
            try {
                if (!$this->blacklist) {
                    throw new Exception("Blacklist not initialized properly");
                }
                $added = $this->blacklist->updateFromUrl($source);
                $totalAdded += $added;
            } catch (Exception $e) {
                $errors[] = "Error updating from $source: " . $e->getMessage();
                error_log($e->getMessage());
            }
        }

        return [
            'success' => empty($errors),
            'total_added' => $totalAdded,
            'errors' => $errors
        ];
    }

    public function syncWithBotDetector($botDetector) {
        try {
            if (!($botDetector instanceof BotDetector)) {
                throw new Exception("Invalid BotDetector instance provided");
            }
            $botDetector->loadWhitelist();
            $botDetector->loadBlacklist();
            $this->whitelist->loadList();
            $this->blacklist->loadList();
        } catch (Exception $e) {
            error_log("Error syncing with BotDetector: " . $e->getMessage());
            throw $e;
        }
    }
}

// Example usage
try {
    $ipBlockList = new IpBlockList();
    $sources = [
        "http://myip.ms/files/blacklist/htaccess/latest_blacklist.txt",
        "http://myip.ms/files/blacklist/htaccess/latest_blacklist_users_submitted.txt"
    ];

    $result = $ipBlockList->updateBlacklist($sources);
    if ($result['success']) {
        error_log("Successfully updated blacklist with {$result['total_added']} new entries.");
    } else {
        error_log("Errors during blacklist update: " . implode(", ", $result['errors']));
    }

} catch (Exception $e) {
    error_log("Fatal error in IP blacklist update: " . $e->getMessage());
}
?>