<?php
/**
 * Scale optimizer helpers for high traffic Telegram bot installations.
 * Safe to include multiple times; keeps old bot.php entrypoints unchanged.
 */

if(!defined('BOT_TELEGRAM_CONNECT_TIMEOUT')) define('BOT_TELEGRAM_CONNECT_TIMEOUT', 6);
if(!defined('BOT_TELEGRAM_TIMEOUT')) define('BOT_TELEGRAM_TIMEOUT', 16);
if(!defined('BOT_SCALE_CACHE_TTL_USERS')) define('BOT_SCALE_CACHE_TTL_USERS', 300);
if(!defined('BOT_SCALE_CACHE_TTL_STATS')) define('BOT_SCALE_CACHE_TTL_STATS', 60);
if(!defined('BOT_SCALE_LOG_SAMPLE_EVERY')) define('BOT_SCALE_LOG_SAMPLE_EVERY', 25);
if(!defined('BOT_SCALE_LOG_MIN_INTERVAL')) define('BOT_SCALE_LOG_MIN_INTERVAL', 300);
// در ممبرگیر، جوین اجباری نباید بعد از یک بار تأیید دائمی فرض شود.
// به‌صورت پیش‌فرض هر تعامل خصوصی کاربر وضعیت عضویت را تازه از تلگرام چک می‌کند.
if(!defined('BOT_FORCED_JOIN_ALWAYS_FRESH')) define('BOT_FORCED_JOIN_ALWAYS_FRESH', true);
if(!defined('BOT_FORCED_JOIN_CACHE_TTL')) define('BOT_FORCED_JOIN_CACHE_TTL', 0);
if(!defined('BOT_LEFT_CHECK_INTERVAL')) define('BOT_LEFT_CHECK_INTERVAL', 600);

function bot_scale_root(){ return dirname(__DIR__, 2); }
function bot_scale_cache_dir(){ return bot_scale_root() . '/cache'; }
function bot_scale_runtime_dir(){ return bot_scale_cache_dir() . '/runtime'; }

function bot_scale_bootstrap(){
    $cache = bot_scale_cache_dir();
    $runtime = bot_scale_runtime_dir();
    if(!is_dir($cache)) @mkdir($cache, 0775, true);
    if(!is_dir($runtime)) @mkdir($runtime, 0775, true);
    if(!is_file($cache.'/.htaccess')) @file_put_contents($cache.'/.htaccess', "Deny from all\n");
    @ini_set('default_socket_timeout', (string)(defined('BOT_TELEGRAM_TIMEOUT') ? BOT_TELEGRAM_TIMEOUT : 16));
    @ini_set('memory_limit', defined('BOT_MEMORY_LIMIT') ? BOT_MEMORY_LIMIT : '256M');
}

function bot_scale_safe_key($key){
    $key = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', (string)$key);
    $key = trim($key, '_');
    return $key === '' ? 'k_'.substr(sha1((string)$key),0,12) : $key;
}
function bot_scale_cache_path($group, $key){
    $dir = bot_scale_cache_dir(); if(!is_dir($dir)) @mkdir($dir,0775,true);
    $group = bot_scale_safe_key($group ?: 'general');
    $safe = bot_scale_safe_key($key);
    return $dir . '/' . $group . '_' . $safe . '_' . substr(sha1($group.'|'.(string)$key), 0, 12) . '.json';
}
function bot_scale_cache_get($group, $key, $ttl = 60){
    $file = bot_scale_cache_path($group, $key);
    if(!is_file($file)) return null;
    $raw = @file_get_contents($file);
    $data = json_decode((string)$raw, true);
    if(!is_array($data) || !isset($data['created_at'])) return null;
    if($ttl > 0 && time() - (int)$data['created_at'] > (int)$ttl){ @unlink($file); return null; }
    return $data['payload'] ?? null;
}
function bot_scale_cache_set($group, $key, $payload){
    $file = bot_scale_cache_path($group, $key);
    $tmp = $file.'.tmp';
    @file_put_contents($tmp, json_encode(['created_at'=>time(), 'payload'=>$payload], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX);
    @rename($tmp, $file);
    return true;
}
function bot_scale_cache_clear_group($group){
    $dir = bot_scale_cache_dir(); if(!is_dir($dir)) return 0;
    $prefix = bot_scale_safe_key($group ?: 'general') . '_';
    $n=0; foreach(glob($dir.'/'.$prefix.'*.json') ?: [] as $f){ if(@unlink($f)) $n++; }
    return $n;
}


function bot_scale_runtime_state_path($group, $key){
    $dir = bot_scale_runtime_dir(); if(!is_dir($dir)) @mkdir($dir,0775,true);
    return $dir . '/' . bot_scale_safe_key($group) . '_' . substr(sha1((string)$key), 0, 20) . '.json';
}
function bot_scale_should_run_user_task($userId, $task, $intervalSeconds){
    $userId = (string)$userId;
    $task = bot_scale_safe_key($task ?: 'task');
    $intervalSeconds = max(1, (int)$intervalSeconds);
    if($userId === '' || $userId === '0') return true;
    $file = bot_scale_runtime_state_path('task_'.$task, $userId);
    $now = time();
    $last = 0;
    if(is_file($file)){
        $data = json_decode((string)@file_get_contents($file), true);
        if(is_array($data)) $last = (int)($data['last_ts'] ?? 0);
    }
    if($last > 0 && ($now - $last) < $intervalSeconds) return false;
    @file_put_contents($file, json_encode(['user_id'=>$userId,'task'=>$task,'last_ts'=>$now], JSON_UNESCAPED_UNICODE), LOCK_EX);
    return true;
}

function bot_scale_log_api_error($method, $httpCode, $raw){
    // Avoid huge error_log growth at 100k users. Log first occurrence, then every Nth or every interval.
    $desc = (string)$raw;
    $decoded = json_decode($desc, true);
    if(is_array($decoded) && isset($decoded['description'])) $desc = (string)$decoded['description'];
    $desc = substr($desc, 0, 220);
    $hash = substr(sha1((string)$method.'|'.(string)$httpCode.'|'.$desc), 0, 16);
    $file = bot_scale_runtime_dir() . '/api_error_' . $hash . '.json';
    $now = time(); $state = ['count'=>0,'last_log'=>0];
    if(is_file($file)){
        $tmp = json_decode((string)@file_get_contents($file), true);
        if(is_array($tmp)) $state = array_merge($state, $tmp);
    }
    $state['count'] = (int)$state['count'] + 1;
    $should = $state['count'] === 1
        || ($state['count'] % (int)BOT_SCALE_LOG_SAMPLE_EVERY) === 0
        || ($now - (int)$state['last_log']) >= (int)BOT_SCALE_LOG_MIN_INTERVAL;
    if($should){
        $state['last_log'] = $now;
        error_log('Telegram API error sampled ['.$method.'] HTTP '.$httpCode.' count='.$state['count'].': '.$desc);
    }
    @file_put_contents($file, json_encode($state, JSON_UNESCAPED_UNICODE), LOCK_EX);
}

function bot_scale_get_user_ids_fast($ttl = null){
    $ttl = $ttl === null ? (int)BOT_SCALE_CACHE_TTL_USERS : (int)$ttl;
    $cached = bot_scale_cache_get('users', 'all_ids_v2', $ttl);
    if(is_array($cached)) return array_values(array_map('strval', $cached));
    $ids = [];
    if(function_exists('bot_sql_pdo')){
        $pdo = bot_sql_pdo();
        if($pdo){
            try{
                $st = $pdo->query('SELECT user_id FROM bot_users ORDER BY user_id ASC');
                $ids = $st->fetchAll(PDO::FETCH_COLUMN) ?: [];
            }catch(Throwable $e){ error_log('Scale user ids SQL failed: '.$e->getMessage()); }
        }
    }
    if(!$ids){
        $files = function_exists('bot_data_glob') ? bot_data_glob('melat/*.json') : (glob(bot_scale_root().'/melat/*.json') ?: []);
        foreach($files as $file){ $id = basename($file, '.json'); if(is_numeric($id) && $id !== '0') $ids[] = (string)$id; }
    }
    $ids = array_values(array_unique(array_filter(array_map('strval', $ids), fn($v)=>$v!=='' && $v!=='0')));
    bot_scale_cache_set('users', 'all_ids_v2', $ids);
    return $ids;
}

function bot_scale_counts($ttl = null){
    $ttl = $ttl === null ? (int)BOT_SCALE_CACHE_TTL_STATS : (int)$ttl;
    $cached = bot_scale_cache_get('stats', 'global_counts_v2', $ttl);
    if(is_array($cached)) return $cached;
    $out = ['created_at'=>time(), 'users'=>0, 'orders'=>0, 'orders_active'=>0, 'orders_done'=>0, 'orders_cancelled'=>0, 'order_members'=>0, 'reward_rows'=>0];
    if(function_exists('bot_sql_pdo')){
        $pdo = bot_sql_pdo();
        if($pdo){
            try{
                $out['users'] = (int)$pdo->query('SELECT COUNT(*) FROM bot_users')->fetchColumn();
                $out['orders'] = (int)$pdo->query('SELECT COUNT(*) FROM bot_orders')->fetchColumn();
                $out['orders_active'] = (int)$pdo->query("SELECT COUNT(*) FROM bot_orders WHERE status IN ('active','on','true','1','run','running','انجام') OR status IS NULL OR status='' ")->fetchColumn();
                $out['orders_done'] = (int)$pdo->query("SELECT COUNT(*) FROM bot_orders WHERE status IN ('done','completed','complete','finish','finished','✅','تکمیل')")->fetchColumn();
                $out['orders_cancelled'] = (int)$pdo->query("SELECT COUNT(*) FROM bot_orders WHERE status IN ('cancel','cancelled','canceled','❌','لغو')")->fetchColumn();
                $out['order_members'] = (int)$pdo->query('SELECT COUNT(*) FROM bot_order_members')->fetchColumn();
                $out['reward_rows'] = (int)$pdo->query('SELECT COUNT(*) FROM bot_channel_rewards')->fetchColumn();
            }catch(Throwable $e){ error_log('Scale counts SQL failed: '.$e->getMessage()); }
        }
    }
    if(!$out['users']) $out['users'] = count(bot_scale_get_user_ids_fast($ttl));
    bot_scale_cache_set('stats', 'global_counts_v2', $out);
    return $out;
}

function bot_scale_warmup_cache(){
    $started = microtime(true);
    $ids = bot_scale_get_user_ids_fast(0);
    $counts = bot_scale_counts(0);
    return [
        'ok'=>true,
        'users_cached'=>count($ids),
        'counts'=>$counts,
        'elapsed_seconds'=>round(microtime(true)-$started, 3),
        'cache_dir'=>bot_scale_cache_dir(),
    ];
}

function bot_scale_clear_all_cache(){
    $dir = bot_scale_cache_dir(); $n=0;
    foreach(glob($dir.'/*.json') ?: [] as $f){ if(@unlink($f)) $n++; }
    foreach(glob(bot_scale_runtime_dir().'/*.json') ?: [] as $f){ if(@unlink($f)) $n++; }
    return $n;
}
