<?php

/**
 * ─────────────────────────────────────────────────────────────────
 *  IVT (Invalid Traffic) Checker
 *
 *  Two checks, in order:
 *    1. IP blacklist  — checks Redis (whitelist first, then blacklist)
 *    2. Bot UA check  — matches user agent against known bot patterns
 *
 *  Redis keys used:
 *    blacklist_ips
 *    ivt_ip_whitelist
 *
 *  Usage:
 *    require_once '/path/to/ivt_check.php';
 *
 *    $ivt = checkIvt();           // run both checks
 *    if ($ivt['blocked']) {
 *        // $ivt['reason'] is 'blacklisted_ip' or 'bot_agent'
 *        // $ivt['detail'] has the matched value
 *        header('HTTP/1.1 403 Forbidden');
 *        exit;
 *    }
 * ─────────────────────────────────────────────────────────────────
 */

// ── Redis config ──────────────────────────────────────────────────
define('IVT_REDIS_HOST', '127.0.0.1');
define('IVT_REDIS_PORT', 22122);

// ── Bot user-agent patterns ───────────────────────────────────────
define('IVT_BOT_PATTERNS', [
    'acunetix', 'ahrefsbot', 'alexa', 'anthropic', 'api-client',
    'apibot', 'apns', 'apps-script', 'automate', 'badbot',
    'badcrawler', 'baidu', 'baiduspider', 'bingbot', 'bot',
    'claude', 'cloudflare', 'content-scraper', 'crawl', 'crawler',
    'curl', 'dataharvest', 'devtool', 'downloader', 'duckduckbot',
    'duckduckgo', 'emailharvest', 'evilbot', 'facebook',
    'facebookcatalog', 'facebookexternalhit', 'fetch',
    'filedownloader', 'go-http-client', 'google-adwords',
    'google-apps-script', 'google-read-aloud', 'googlebot',
    'gptbot', 'grabber', 'harvester', 'headless', 'headlesschrome',
    'healthchecker', 'httpclient', 'httrack', 'java', 'libcurl',
    'linkchecker', 'meta-externalads', 'meta-externalagent',
    'meta-externalfetcher', 'meta-webindexer', 'mj12bot', 'moz',
    'netsparker', 'okhttp', 'perplexitybot', 'phantomjs', 'pingdm',
    'pingdom', 'postman', 'proximic', 'proxy', 'proxybot',
    'proxylord', 'python-requests', 'randomagent', 'restclient',
    'scan', 'scrape', 'scraper', 'scrapy', 'selenium',
    'semrushbot', 'seo', 'site-monitor', 'sitechecker', 'sitemap',
    'skypeuripreview', 'slack', 'slackbot', 'slurp', 'spider',
    'spiderman', 'sqlmap', 'statuscake', 'testagent', 'tool',
    'turnitin', 'uptimerobot', 'urllib', 'useragent', 'urltool',
    'webcrawler', 'webtool', 'wget', 'xmlsitemap', 'yahoobot',
    'yandex', 'yandexbot',
]);
// ─────────────────────────────────────────────────────────────────


// ═════════════════════════════════════════════════════════════════
//  PUBLIC API
//  Returns an array:
//    [
//      'blocked' => bool,
//      'reason'  => 'blacklisted_ip' | 'bot_agent' | null,
//      'detail'  => string|null,
//      'ip'      => string|null,
//      'ua'      => string|null,
//    ]
// ═════════════════════════════════════════════════════════════════
function checkIvt(bool $debug = false): array
{
    $ip = ivt_get_ip();
    $ua = ivt_get_ua();

    $result = [
        'blocked' => false,
        'reason'  => null,
        'detail'  => null,
        'ip'      => $ip,
        'ua'      => $ua,
    ];

    // ── Check 1: IP blacklist ─────────────────────────────────────
    $ipCheck = ivt_check_ip($ip, $debug);
    if ($ipCheck['blocked']) {
        $result['blocked'] = true;
        $result['reason']  = 'blacklisted_ip';
        $result['detail']  = $ipCheck['detail'];
        return $result;
    }

    // ── Check 2: Bot user agent ───────────────────────────────────
    $uaCheck = ivt_check_bot($ua, $debug);
    if ($uaCheck['blocked']) {
        $result['blocked'] = true;
        $result['reason']  = 'bot_agent';
        $result['detail']  = $uaCheck['detail'];
        return $result;
    }

    return $result;
}


// ═════════════════════════════════════════════════════════════════
//  CHECK 1 — IP BLACKLIST
// ═════════════════════════════════════════════════════════════════
function ivt_check_ip(?string $ip, bool $debug = false): array
{
    $out = ['blocked' => false, 'detail' => 'Ip not whitelisted or blacklisted'];

    if (!$ip) return $out;

    $ipInt = ivt_ip_to_int($ip);
    if ($ipInt === false) return $out;

    if ($debug) {
        error_log("[IVT] IP check — ip={$ip}  ipInt={$ipInt}");
        return $out;
    }

    try {
        $redis = ivt_redis();

        if ($redis->sIsMember('ivt_ip_whitelist', (string)$ipInt)) {
            return ['blocked' => false, 'detail' => 'Ip is whitelisted'];
        }

        if ($redis->sIsMember('blacklist_ips', (string)$ipInt)) {
            $out['blocked'] = true;
            $out['detail']  = $ipInt . ' Ip is blacklisted';
        }

    } catch (Exception $e) {
        error_log('[IVT] Redis error (IP check): ' . $e->getMessage());
    }

    return $out;
}


// ═════════════════════════════════════════════════════════════════
//  CHECK 2 — BOT USER AGENT
// ═════════════════════════════════════════════════════════════════
function ivt_check_bot(?string $ua, bool $debug = false): array
{
    $out = ['blocked' => false, 'detail' => 'Not a bot'];

    if (!$ua) return $out;

    $escaped = array_map('preg_quote', IVT_BOT_PATTERNS);
    $pattern = '/(^|[^a-zA-Z0-9])(' . implode('|', $escaped) . ')([^a-zA-Z0-9]|$)/i';

    if (preg_match($pattern, $ua, $matches)) {
        if ($debug) {
            error_log("[IVT] Bot match — pattern={$matches[2]}  ua={$ua}");
            return $out;
        }
        $out['blocked'] = true;
        $out['detail']  = $matches[2];
    }

    return $out;
}


// ═════════════════════════════════════════════════════════════════
//  HELPERS
// ═════════════════════════════════════════════════════════════════

function ivt_get_ip(): ?string
{
    $raw = $_SERVER['HTTP_X_FORWARDED_FOR']
        ?? $_SERVER['HTTP_CF_CONNECTING_IP']
        ?? $_SERVER['REMOTE_ADDR']
        ?? '';

    $ip = trim(explode(',', $raw)[0]);

    if (!empty($_GET['ip'])) $ip = trim($_GET['ip']);

    return $ip !== '' ? $ip : null;
}

function ivt_get_ua(): ?string
{
    $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
    if (!empty($_GET['ua'])) $ua = $_GET['ua'];
    return $ua !== '' ? $ua : null;
}

function ivt_ip_to_int(string $ip)
{
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
        return ivt_ip6_to_int($ip);
    }
    $int = ip2long($ip);
    return $int !== false ? $int : false;
}

function ivt_ip6_to_int(string $ip): string
{
    $packed = inet_pton($ip);
    $bin    = '';
    for ($i = strlen($packed) - 1; $i >= 0; $i--) {
        $bin = sprintf('%08b', ord($packed[$i])) . $bin;
    }
    if (function_exists('gmp_init')) {
        return gmp_strval(gmp_init($bin, 2), 10);
    }
    if (function_exists('bcadd')) {
        $dec = '0';
        for ($i = 0; $i < strlen($bin); $i++) {
            $dec = bcmul($dec, '2', 0);
            $dec = bcadd($dec, $bin[$i], 0);
        }
        return $dec;
    }
    trigger_error('[IVT] GMP or BCMath extension required for IPv6 support.', E_USER_ERROR);
}

function ivt_redis(): Redis
{
    static $redis = null;
    if ($redis === null) {
        $redis = new Redis();
        $redis->pconnect(IVT_REDIS_HOST, IVT_REDIS_PORT);
    }
    return $redis;
}
