<?php

/**
 * Geo access — South Africa (ZA) + Morocco (MA).
 * Settings: auth/cache/geo/geo_countries.cfg (editable on cPanel).
 */
class GeoAccess
{
    /** @var string[] */
    public static $allowedCodes = array('ZA', 'MA');

    /** @var array<string, array{code:string,name:string,allowed:bool}> */
    private static $cache = array();

    /** @var array{allowed: string[], fail_open: bool, geo_off: bool}|null */
    private static $settings = null;

    private static function settingsPath()
    {
        return dirname(__DIR__) . '/cache/geo/geo_countries.cfg';
    }

    private static function loadSettings()
    {
        if (self::$settings !== null) {
            return self::$settings;
        }

        $allowed = array('ZA', 'MA');
        $failOpen = true;
        $geoOff = false;

        $file = self::settingsPath();
        if (is_readable($file)) {
            $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
            $fromFile = array();
            foreach ($lines as $line) {
                $line = trim($line);
                if ($line === '' || $line[0] === '#') {
                    continue;
                }
                if (stripos($line, 'fail_open=') === 0) {
                    $failOpen = trim(substr($line, 10)) === '1';
                    continue;
                }
                if (stripos($line, 'geo_off=') === 0) {
                    $geoOff = trim(substr($line, 8)) === '1';
                    continue;
                }
                $code = strtoupper(preg_replace('/[^A-Z]/', '', $line));
                if (strlen($code) === 2) {
                    $fromFile[] = $code;
                }
            }
            if (!empty($fromFile)) {
                $allowed = array_values(array_unique($fromFile));
            }
        }

        self::$allowedCodes = $allowed;
        self::$settings = array(
            'allowed' => $allowed,
            'fail_open' => $failOpen,
            'geo_off' => $geoOff,
        );

        return self::$settings;
    }

    public static function isGeoDisabled()
    {
        return !empty(self::loadSettings()['geo_off']);
    }

    public static function normalizeIp($ip)
    {
        $ip = trim((string) $ip);
        if ($ip === '::1' || $ip === '') {
            return '127.0.0.1';
        }
        return $ip;
    }

    public static function resolveClientIp()
    {
        foreach (array(
            'HTTP_CF_CONNECTING_IP',
            'HTTP_X_REAL_IP',
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_FORWARDED',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED',
            'REMOTE_ADDR',
        ) as $key) {
            if (empty($_SERVER[$key])) {
                continue;
            }
            foreach (explode(',', (string) $_SERVER[$key]) as $candidate) {
                $candidate = self::normalizeIp(trim($candidate));
                if ($candidate === '127.0.0.1') {
                    return $candidate;
                }
                if (filter_var($candidate, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
                    return $candidate;
                }
            }
        }

        $fallback = isset($_SERVER['REMOTE_ADDR']) ? self::normalizeIp($_SERVER['REMOTE_ADDR']) : '127.0.0.1';
        return filter_var($fallback, FILTER_VALIDATE_IP) ? $fallback : '127.0.0.1';
    }

    public static function isLocalIp($ip)
    {
        $ip = self::normalizeIp($ip);
        if ($ip === '127.0.0.1') {
            return true;
        }
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
            return true;
        }
        return false;
    }

    private static function cacheDir()
    {
        $dir = dirname(__DIR__) . '/cache/geo';
        if (!is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
        return $dir;
    }

    private static function readFileCache($ip)
    {
        $file = self::cacheDir() . '/' . md5($ip) . '.json';
        if (!is_file($file)) {
            return null;
        }
        $raw = @file_get_contents($file);
        if ($raw === false || $raw === '') {
            return null;
        }
        $data = json_decode($raw, true);
        if (!is_array($data) || empty($data['ts']) || empty($data['info'])) {
            return null;
        }
        if ((time() - (int) $data['ts']) > 86400) {
            return null;
        }
        return $data['info'];
    }

    private static function writeFileCache($ip, array $info)
    {
        $file = self::cacheDir() . '/' . md5($ip) . '.json';
        @file_put_contents($file, json_encode(array(
            'ts' => time(),
            'info' => $info,
        )));
    }

    private static function curlJson($url, $timeout = 2)
    {
        if (!function_exists('curl_init')) {
            return null;
        }
        $ch = curl_init();
        curl_setopt_array($ch, array(
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_CONNECTTIMEOUT => 1,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; GeoAccess/1.0)',
        ));
        $response = curl_exec($ch);
        curl_close($ch);
        if (!$response) {
            return null;
        }
        $data = json_decode($response, true);
        return is_array($data) ? $data : null;
    }

    private static function buildInfo($code, $name)
    {
        self::loadSettings();
        $code = strtoupper(trim((string) $code));
        if ($code === '' || strlen($code) !== 2) {
            $code = 'XX';
        }
        $name = trim((string) $name);
        if ($name === '') {
            $name = $code === 'XX' ? 'Unknown' : $code;
        }

        return array(
            'code' => $code,
            'name' => $name,
            'allowed' => in_array($code, self::$allowedCodes, true),
        );
    }

    private static function lookupFromApis($ip)
    {
        $encoded = rawurlencode($ip);

        // Fastest first — avoids cPanel timeout on slow outbound HTTP
        $data = self::curlJson('https://ipwho.is/' . $encoded, 2);
        if (is_array($data) && !empty($data['success']) && !empty($data['country_code'])) {
            return self::buildInfo($data['country_code'], $data['country'] ?? $data['country_code']);
        }

        $data = self::curlJson('http://ip-api.com/json/' . $encoded . '?fields=status,countryCode,country', 2);
        if (is_array($data) && isset($data['status']) && $data['status'] === 'success' && !empty($data['countryCode'])) {
            return self::buildInfo($data['countryCode'], $data['country'] ?? $data['countryCode']);
        }

        return null;
    }

    public static function lookup($ip)
    {
        $ip = self::normalizeIp($ip);
        self::loadSettings();

        if (isset(self::$cache[$ip])) {
            return self::$cache[$ip];
        }

        if (self::isGeoDisabled()) {
            return self::$cache[$ip] = array(
                'code' => 'OFF',
                'name' => 'Geo disabled',
                'allowed' => true,
            );
        }

        if (self::isLocalIp($ip)) {
            return self::$cache[$ip] = array(
                'code' => 'LOCAL',
                'name' => 'Localhost',
                'allowed' => true,
            );
        }

        $cached = self::readFileCache($ip);
        if (is_array($cached)) {
            return self::$cache[$ip] = $cached;
        }

        $info = self::lookupFromApis($ip);
        if ($info === null) {
            $settings = self::loadSettings();
            $info = array(
                'code' => 'XX',
                'name' => 'Unknown',
                'allowed' => !empty($settings['fail_open']),
            );
        }

        self::writeFileCache($ip, $info);
        return self::$cache[$ip] = $info;
    }

    public static function isAllowed($ip)
    {
        if (self::isGeoDisabled()) {
            return true;
        }
        $info = self::lookup($ip);
        return !empty($info['allowed']);
    }

    public static function flagEmoji($countryCode)
    {
        $countryCode = strtoupper(trim((string) $countryCode));
        if ($countryCode === 'LOCAL' || $countryCode === 'OFF') {
            return '🖥';
        }
        if (strlen($countryCode) !== 2) {
            return '🌍';
        }

        $flag = '';
        for ($i = 0; $i < 2; $i++) {
            $char = $countryCode[$i];
            if ($char < 'A' || $char > 'Z') {
                return '🌍';
            }
            $codePoint = 0x1F1E6 + (ord($char) - ord('A'));
            if (function_exists('mb_chr')) {
                $flag .= mb_chr($codePoint, 'UTF-8');
            } else {
                $flag .= json_decode('"' . '\\u' . sprintf('%04X', $codePoint) . '"');
            }
        }
        return $flag;
    }

    public static function telegramIpLine($ip)
    {
        $ip = self::normalizeIp($ip);
        $info = self::lookup($ip);
        $flag = self::flagEmoji($info['code']);
        return $flag . ' ' . $info['name'] . ' • `' . $ip . '`';
    }

    public static function denyAccess()
    {
        if (!headers_sent()) {
            http_response_code(403);
        }
        exit('Access Denied');
    }
}
