<?php

class ProxyHelper {

    private $cookieFile;
    private $cookieJar;
    private $userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36';
    private $activeProfile = 'default';
    private $activeOfficeFile;
    private $activeUpazilaFile;

    public function __construct(){

        if(session_status()!==PHP_SESSION_ACTIVE && !headers_sent()){
            @session_start();
        }

        $cookiesDir = dirname(__DIR__).'/cookies';
        if(!is_dir($cookiesDir)){
            @mkdir($cookiesDir,0755,true);
        }

        $activeProfileFile = $cookiesDir . "/active_profile.txt";
        $rawProfile = '';
        if (file_exists($activeProfileFile)) {
            $rawProfile = trim((string)file_get_contents($activeProfileFile));
        }
        if ($rawProfile === '') {
            $rawProfile = isset($_SESSION['cookie_profile']) ? (string)$_SESSION['cookie_profile'] : '';
        }
        $profile = $rawProfile !== '' ? preg_replace('/[^a-zA-Z0-9_-]/', '', $rawProfile) : 'default';
        if ($profile === '') $profile = 'default';

        if (session_status() === PHP_SESSION_ACTIVE) {
            $_SESSION['cookie_profile'] = $profile;
        }

        $explicitProfile = ($rawProfile !== '' && $profile !== 'default');
        $this->cookieFile = $cookiesDir."/cookie_data_{$profile}.json";

        $isUsable = false;
        if (file_exists($this->cookieFile)) {
            $data = json_decode((string)file_get_contents($this->cookieFile), true);
            $cs = is_array($data) ? trim((string)($data['cookie_string'] ?? '')) : '';
            if ($cs !== '' && stripos($cs, 'SESSION=') !== false) {
                $isUsable = true;
            }
            $ls = is_array($data) ? strtolower(trim((string)($data['last_status'] ?? ''))) : '';
            if ($ls === 'blocked' || $ls === 'inactive' || $ls === 'expired') {
                $isUsable = false;
            }
        }

        if (!$isUsable) {
            $files = glob($cookiesDir . "/cookie_data_*.json");
            if ($files) {
                usort($files, function($a, $b) {
                    return filemtime($b) - filemtime($a);
                });
                foreach ($files as $f) {
                    $d = json_decode((string)file_get_contents($f), true);
                    $cs = is_array($d) ? trim((string)($d['cookie_string'] ?? '')) : '';
                    $ls = is_array($d) ? strtolower(trim((string)($d['last_status'] ?? ''))) : '';
                    if ($ls === 'blocked' || $ls === 'inactive' || $ls === 'expired') {
                        continue;
                    }
                    if ($cs !== '' && stripos($cs, 'SESSION=') !== false) {
                        $this->cookieFile = $f;
                        $basename = basename($f);
                        if (preg_match('/cookie_data_(.+)\.json/', $basename, $m)) {
                            $foundProfile = $m[1];
                            if (session_status() === PHP_SESSION_ACTIVE) {
                                $_SESSION['cookie_profile'] = $foundProfile;
                            }
                            @file_put_contents($activeProfileFile, $foundProfile);
                            @file_put_contents(dirname($cookiesDir) . "/bdris_cookie.txt", $cs);
                        }
                        break;
                    }
                }
            }
        }

        $this->activeProfile = $profile;
        
        // Use profile-specific office and upazila files
        $this->activeOfficeFile = $cookiesDir . "/active_office_{$profile}.txt";
        $this->activeUpazilaFile = $cookiesDir . "/active_upazila_{$profile}.txt";

        // Auto-cleanup cached office token when switching profiles or cookies are updated
        $lastProfileFile = $cookiesDir . "/last_used_profile.txt";
        $lastProfile = file_exists($lastProfileFile) ? trim(file_get_contents($lastProfileFile)) : '';
        
        $clearCache = false;
        if ($lastProfile !== $profile) {
            $clearCache = true;
        } else if (file_exists($this->cookieFile) && file_exists($this->activeOfficeFile)) {
            if (filemtime($this->cookieFile) > filemtime($this->activeOfficeFile)) {
                $clearCache = true;
            }
        }
        
        if ($clearCache) {
            // ✅ active_office.txt DELETE করা হবে না - integer officeId সংরক্ষণ করা থাকবে
            // শুধু upazila cache ক্লিয়ার করো
            @unlink($this->activeUpazilaFile);
            @file_put_contents($lastProfileFile, $profile);
        }

        $profileForJar = $profile;
        $baseCookieFile = basename((string)$this->cookieFile);
        if (preg_match('/cookie_data_(.+)\.json/', $baseCookieFile, $m2)) {
            $profileForJar = preg_replace('/[^a-zA-Z0-9_-]/','', (string)$m2[1]);
            if ($profileForJar === '') $profileForJar = $profile;
        }
        $this->cookieJar  = $cookiesDir."/cookie_jar_{$profileForJar}.txt";

        // Auto-ping BDRIS to keep the session alive on the server
        $lastPingFile = $cookiesDir . "/last_ping_{$profileForJar}.txt";
        $now = time();
        $lastPing = file_exists($lastPingFile) ? (int)file_get_contents($lastPingFile) : 0;
        if ($now - $lastPing > 300) { // every 5 minutes
            @file_put_contents($lastPingFile, $now);
            $cookie = $this->getCookies();
            if ($cookie !== '') {
                $ch = curl_init('https://bdris.gov.bd/api/user/profile');
                curl_setopt_array($ch, [
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_TIMEOUT => 2,
                    CURLOPT_CONNECTTIMEOUT => 2,
                    CURLOPT_SSL_VERIFYPEER => false,
                    CURLOPT_SSL_VERIFYHOST => false,
                    CURLOPT_COOKIE => $cookie,
                    CURLOPT_HTTPHEADER => [
                        'User-Agent: ' . $this->getUserAgent(),
                        'client: bris',
                        'X-Requested-With: XMLHttpRequest'
                    ]
                ]);
                curl_exec($ch);
                // curl_close($ch); // Deprecated in PHP 8.5+ and auto-closed by garbage collector
            }
        }
    }

    public function getActiveProfile() {
        return $this->activeProfile;
    }

    public function getActiveOfficeFile() {
        return $this->activeOfficeFile;
    }

    public function getActiveUpazilaFile() {
        return $this->activeUpazilaFile;
    }

    public function getCookies(){

        if(file_exists($this->cookieFile)){
            $data=json_decode(file_get_contents($this->cookieFile),true);
            if(!empty($data['cookie_string'])){
                return trim($data['cookie_string']);
            }
        }

        $sessionJson=dirname(__DIR__).'/session.json';

        if(file_exists($sessionJson)){
            $data=json_decode(file_get_contents($sessionJson),true);
            if(!empty($data['cookie'])){
                return trim($data['cookie']);
            }
        }

        return '';
    }

    public function getUserAgent(){

        if(file_exists($this->cookieFile)){
            $data=json_decode(file_get_contents($this->cookieFile),true);
            if(!empty($data['user_agent'])){
                return $data['user_agent'];
            }
        }

        return $this->userAgent;
    }

    public function getCsrfToken(){

        if(file_exists($this->cookieFile)){
            $data=json_decode(file_get_contents($this->cookieFile),true);
            if(!empty($data['csrf_token'])){
                return $data['csrf_token'];
            }
        }

        $sessionJson=dirname(__DIR__).'/session.json';

        if(file_exists($sessionJson)){
            $data=json_decode(file_get_contents($sessionJson),true);
            if(!empty($data['csrf'])){
                return $data['csrf'];
            }
        }

        return null;
    }

    public function getExtraHeaders(){

        if(file_exists($this->cookieFile)){
            $data=json_decode(file_get_contents($this->cookieFile),true);
            if(!empty($data['extra_headers'])){
                return $data['extra_headers'];
            }
        }

        return [];
    }

    public function saveCookies($cookieString, $extraHeaders=[], $csrfToken=null, $userAgent=null) {
        if(empty($cookieString)) return false;

        $data = [
            'cookie_string' => $cookieString,
            'extra_headers' => $extraHeaders,
            'csrf_token' => $csrfToken,
            'user_agent' => $userAgent ?: $this->userAgent,
            'updated_at' => time(),
            'last_status' => 'active',
            'sender_name' => 'Manual Link',
            'browser_name' => 'manual_link'
        ];

        // Ensure directory exists
        if(!file_exists(dirname($this->cookieFile))){
            @mkdir(dirname($this->cookieFile), 0755, true);
        }

        return file_put_contents($this->cookieFile, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
    }

    public function fetch($url, $method = 'GET', $params = [], $isJson = false, $referer = null, $extraHeaders = [], $timeout = 30, $retries = 3, $retryDelay = 2000)
    {
        $ch = curl_init();

        if ($method === 'GET' && !empty($params)) {
            $url .= '?' . http_build_query($params);
        }

        $cookie = $this->getCookies();
        $csrf = $this->getCsrfToken();
        $method = strtoupper((string)$method);
        $isBdrisHost = false;
        $host = '';
        $purl = @parse_url((string)$url);
        if (is_array($purl) && !empty($purl['host'])) {
            $host = strtolower((string)$purl['host']);
            $isBdrisHost = ($host === 'bdris.gov.bd' || str_ends_with($host, '.bdris.gov.bd'));
        }
        $cookieSig = $cookie !== '' ? substr(sha1($cookie), 0, 12) : 'nocookie';

        if ($isBdrisHost) {
            static $lastBdrisMsBySig = [];
            $sig = $cookieSig !== '' ? $cookieSig : 'nocookie';
            $nowMs = (int)floor(microtime(true) * 1000);
            $last = isset($lastBdrisMsBySig[$sig]) ? (int)$lastBdrisMsBySig[$sig] : 0;
            $minDelayMs = 350;
            if ($last > 0) {
                $wait = ($last + $minDelayMs) - $nowMs;
                if ($wait > 0) {
                    usleep((int)$wait * 1000);
                }
            }
            $lastBdrisMsBySig[$sig] = (int)floor(microtime(true) * 1000);

            if ($method === 'GET') {
                static $recentGetCache = [];
                $k = sha1($sig . '|' . (string)$url);
                $nowMs2 = (int)floor(microtime(true) * 1000);
                if (isset($recentGetCache[$k]) && is_array($recentGetCache[$k])) {
                    $age = $nowMs2 - (int)($recentGetCache[$k]['ts'] ?? 0);
                    if ($age >= 0 && $age < 800) {
                        return (array)$recentGetCache[$k]['resp'];
                    }
                }
            }
        }

        $headers = [
            "Accept: application/json, text/javascript, */*; q=0.01",
            "X-Requested-With: XMLHttpRequest",
            "User-Agent: " . $this->getUserAgent(),
            "Connection: keep-alive",
            "Sec-Fetch-Dest: empty",
            "Sec-Fetch-Mode: cors",
            "Sec-Fetch-Site: same-origin"
        ];

        if ($isBdrisHost) {
            $headers[] = "client: bris";
            $headers[] = "Accept-Language: en-US,en;q=0.9,bn;q=0.8";
        }

        // Forward user IP to bypass some IP checks
        if (!empty($_SERVER['REMOTE_ADDR'])) {
            $headers[] = "X-Forwarded-For: " . $_SERVER['REMOTE_ADDR'];
            $headers[] = "Client-IP: " . $_SERVER['REMOTE_ADDR'];
        }

        if ($csrf) {
            $headers[] = "X-CSRF-TOKEN: " . $csrf;
        }

        if (!empty($extraHeaders)) {
            $headers = array_merge($headers, $extraHeaders);
        }

        // Normalize headers to prevent duplicates (e.g. two Accept headers)
        $headerMap = [];
        $finalHeaders = [];
        foreach ($headers as $h) {
            $parts = explode(':', $h, 2);
            if (count($parts) === 2) {
                $key = strtolower(trim($parts[0]));
                $headerMap[$key] = trim($parts[0]) . ': ' . trim($parts[1]);
            } else {
                $finalHeaders[] = $h;
            }
        }
        $headers = array_merge($finalHeaders, array_values($headerMap));

        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_ENCODING => "",
            CURLOPT_CONNECTTIMEOUT => 20,
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_DNS_CACHE_TIMEOUT => 120,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_FORBID_REUSE => true,
            CURLOPT_COOKIEJAR => $this->cookieJar,
            CURLOPT_COOKIEFILE => $this->cookieJar
        ]);
        if (defined('CURLOPT_TCP_KEEPALIVE')) {
            @curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1);
        }
        if (defined('CURLOPT_NOSIGNAL')) {
            @curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
        }

        if (!empty($cookie)) {
            curl_setopt($ch, CURLOPT_COOKIE, $cookie);
        }

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);

            if (!empty($params)) {
                if ($isJson) {
                    $json = json_encode($params);
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
                    $headers[] = 'Content-Type: application/json';
                } else {
                    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
                    $headers[] = 'Content-Type: application/x-www-form-urlencoded';
                }
            }
        }

        if ($referer) {
            curl_setopt($ch, CURLOPT_REFERER, $referer);
        }

        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $response = null;
        $err = null;
        $errNo = 0;
        $attempts = 0;
        $retries = (int)$retries;
        if ($retries < 1) $retries = 1;
        $retryDelay = (int)$retryDelay;
        if ($retryDelay < 0) $retryDelay = 0;

        while ($attempts < $retries) {
            $attempts++;
            $response = curl_exec($ch);
            $curl_errno = (int)curl_errno($ch);

            if ($curl_errno === 0) {
                $err = null;
                $errNo = 0;
                break; 
            }

            $err = (string)curl_error($ch);
            $errNo = $curl_errno;

            $isTransient = in_array($curl_errno, [CURLE_RECV_ERROR, CURLE_OPERATION_TIMEDOUT, CURLE_COULDNT_CONNECT, CURLE_SEND_ERROR], true);
            if (!$isTransient || $attempts >= $retries) {
                break;
            }

            $base = (int)round($retryDelay * pow(2, max(0, $attempts - 1)));
            if ($base < 250) $base = 250;
            $max = 8000;
            if ($base > $max) $base = $max;
            $jitter = random_int(0, 250);
            usleep((int)(($base + $jitter) * 1000));
        }

        if ($err) {
            error_log("ProxyHelper CURL Error: $err (errno $errNo) for URL: $url");
            // curl_close($ch); // Automatically closed in PHP 8.0+
            return ['status' => 0, 'body' => '', 'error' => $err];
        }

        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        // curl_close($ch); // Automatically closed in PHP 8.0+

        $out = [
            'status' => $code,
            'body' => $response
        ];

        if ($isBdrisHost && $method === 'GET') {
            static $recentGetCache = [];
            $sig = $cookieSig !== '' ? $cookieSig : 'nocookie';
            $k = sha1($sig . '|' . (string)$url);
            $recentGetCache[$k] = [
                'ts' => (int)floor(microtime(true) * 1000),
                'resp' => $out
            ];
            if (count($recentGetCache) > 60) {
                $recentGetCache = array_slice($recentGetCache, -40, null, true);
            }
        }

        return $out;
    }

    public function fetchTryBoth($url,$params=[],$referer=null){

        $resp=$this->fetch($url,'POST',$params,true,$referer);

        if(($resp['status']??0)!=200){
            $resp=$this->fetch($url,'POST',$params,false,$referer);
        }

        return $resp;
    }

    public function parseJson($resp){

        if(empty($resp['body'])) return null;

        $data=json_decode($resp['body'],true);

        return json_last_error()===JSON_ERROR_NONE?$data:null;
    }

    public function parseResponse($resp){

        $json=$this->parseJson($resp);

        if($json) return $json;

        return null;
    }

    public function getIntegerOfficeId($officeToken) {
        if (empty($officeToken)) return '';
        if (strlen($officeToken) <= 15 && ctype_digit((string)$officeToken)) return $officeToken; // already integer
        
        $cacheFile = dirname(__DIR__) . '/cache/office_mappings.json';
        $mappings = [];
        if (file_exists($cacheFile)) {
            $mappings = json_decode(file_get_contents($cacheFile), true);
        }
        
        // Look up by token in mappings
        if (is_array($mappings)) {
            foreach ($mappings as $geoId => $data) {
                if (is_array($data) && ($data['token'] ?? '') === $officeToken) {
                    return $data['officeId'];
                }
            }
        }
        
        // If not found in cache, fetch search page to parse
        $urlPage = "https://bdris.gov.bd/admin/br/applications/search?data=" . urlencode($officeToken);
        $baseHeaders = [
            'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language: en-US,en;q=0.9,bn;q=0.8'
        ];
        $respPage = $this->fetch($urlPage, 'GET', [], false, 'https://bdris.gov.bd/admin/br/applications', $baseHeaders);
        if (($respPage['status'] ?? 0) === 200) {
            $html = $respPage['body'] ?? '';
            $intOfficeId = null;
            if (preg_match('/(?:const|var|let)\s+officeId\s*=\s*([0-9]+)/i', $html, $m)) {
                $intOfficeId = $m[1];
            }
            if (empty($intOfficeId) || $intOfficeId == 'null') {
                if (preg_match('/"officeId":\s*([0-9]+)/i', $html, $m)) {
                    $intOfficeId = $m[1];
                }
            }
            if (!empty($intOfficeId)) {
                // Ensure cache dir exists
                $cacheDir = dirname($cacheFile);
                if (!is_dir($cacheDir)) {
                    @mkdir($cacheDir, 0755, true);
                }
                // Save to mappings (temp geoId)
                $mappings['temp_' . time()] = [
                    'token' => $officeToken,
                    'officeId' => $intOfficeId
                ];
                @file_put_contents($cacheFile, json_encode($mappings, JSON_PRETTY_PRINT));
                return $intOfficeId;
            }
        }
        return '';
    }

    public function getOfficeToken($officeId) {
        if (empty($officeId)) return '';
        if (strlen($officeId) > 15) return $officeId; // already token
        
        // Cookie hash দিয়ে ক্যাশ validate করো — সেশন বদলালে token-ও বদলায়
        $cookieSig = substr(sha1((string)$this->getCookies()), 0, 12);
        
        $cacheFile = dirname(__DIR__) . '/cache/office_token_cache.json';
        if (file_exists($cacheFile)) {
            $cache = json_decode(file_get_contents($cacheFile), true);
            if (is_array($cache) && ($cache['cookie_sig'] ?? '') === $cookieSig 
                && ($cache['office_id'] ?? '') == $officeId 
                && !empty($cache['token'])
                && (time() - ($cache['ts'] ?? 0)) < 1800) { // 30 min TTL
                return $cache['token'];
            }
        }
        
        // Dashboard থেকে fresh token আনো
        $dashResp = $this->fetch('https://bdris.gov.bd/br/application', 'GET', [], false, 'https://bdris.gov.bd/');
        $dashBody = (string)($dashResp['body'] ?? '');
        $activeToken = '';
        if (preg_match('/\/admin\/br\/applications\/search\?data=([A-Za-z0-9_\-]+)/i', $dashBody, $m)) {
            $activeToken = $m[1];
        } elseif (preg_match('/\/admin\/user\/chalan\?data=([A-Za-z0-9_\-]+)/i', $dashBody, $m)) {
            $activeToken = $m[1];
        }
        
        // ক্যাশ আপডেট করো
        if ($activeToken !== '') {
            $cacheDir = dirname($cacheFile);
            if (!is_dir($cacheDir)) @mkdir($cacheDir, 0755, true);
            @file_put_contents($cacheFile, json_encode([
                'office_id' => $officeId,
                'token' => $activeToken,
                'cookie_sig' => $cookieSig,
                'ts' => time()
            ], JSON_PRETTY_PRINT));
        }
        
        return $activeToken;
    }

}
