<?php
namespace App;

/**
 * ═══════════════════════════════════════════════════════════════════════════
 * Security - امنیت وبهوک (نسخه سازگار با هاست اشتراکی)
 * ═══════════════════════════════════════════════════════════════════════════
 * 
 * این کلاس امنیت درخواست‌های ورودی را بررسی می‌کند.
 * برای هاست‌های اشتراکی که پشت پراکسی هستند، چک IP غیرفعال شده است.
 */
final class Security
{
    // رنج‌های IP رسمی تلگرام (برای سرورهای مستقیم)
    private const TELEGRAM_CIDRS = [
        '149.154.160.0/20',
        '91.108.4.0/22',
        '91.108.56.0/22',
        '91.108.8.0/22',
        '91.108.12.0/22',
        '91.108.16.0/22',
        '91.108.20.0/22',
    ];

    /**
     * بررسی امنیت درخواست تلگرام
     * 
     * @param string $method HTTP method
     * @param array $server $_SERVER array
     * @param Logger|null $logger Logger instance
     * @throws \RuntimeException if verification fails
     */
    public static function verifyTelegram(string $method, array $server, ?Logger $logger = null): void
    {
        // 1. فقط POST قبول است
        if ($method !== 'POST') {
            self::fail('Method not allowed', $logger);
        }

        // 2. بررسی Secret Token (اگر تنظیم شده)
        $expectedToken = Env::get('TELEGRAM_SECRET_TOKEN', '');
        
        if (!empty($expectedToken)) {
            $givenToken = $server['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '';
            
            if (empty($givenToken) || !hash_equals($expectedToken, $givenToken)) {
                self::fail('Invalid secret token', $logger);
            }
        }

        // 3. بررسی IP (فقط اگر VERIFY_TELEGRAM_IP=1 باشد)
        // پیش‌فرض: غیرفعال - برای هاست‌های اشتراکی که پشت پراکسی هستند
        $verifyIp = Env::get('VERIFY_TELEGRAM_IP', '0') === '1';
        
        if ($verifyIp) {
            $ip = self::getClientIp($server);
            
            if (!self::isIpInCidrs($ip, self::TELEGRAM_CIDRS)) {
                self::fail('IP not allowed: ' . $ip, $logger);
            }
        }

        // 4. بررسی Content-Type
        $contentType = $server['CONTENT_TYPE'] ?? '';
        if (stripos($contentType, 'application/json') === false) {
            // بعضی پراکسی‌ها Content-Type را تغییر می‌دهند - فقط لاگ کن
            if ($logger) {
                $logger->warn('Unexpected content type', ['type' => $contentType]);
            }
        }

        // لاگ موفقیت
        if ($logger) {
            $logger->info('Webhook verified', [
                'ip' => $server['REMOTE_ADDR'] ?? 'unknown'
            ]);
        }
    }

    /**
     * دریافت IP واقعی کلاینت (با در نظر گرفتن پراکسی)
     */
    private static function getClientIp(array $server): string
    {
        // لیست هدرهای معتبر برای IP
        $headers = [
            'HTTP_CF_CONNECTING_IP',      // Cloudflare
            'HTTP_X_REAL_IP',             // Nginx proxy
            'HTTP_X_FORWARDED_FOR',       // Standard proxy
            'REMOTE_ADDR'                 // Direct connection
        ];

        foreach ($headers as $header) {
            if (!empty($server[$header])) {
                $ip = $server[$header];
                
                // X-Forwarded-For می‌تواند چند IP داشته باشد
                if (strpos($ip, ',') !== false) {
                    $ips = explode(',', $ip);
                    $ip = trim($ips[0]);
                }
                
                // اعتبارسنجی IP
                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    return $ip;
                }
            }
        }

        return '0.0.0.0';
    }

    /**
     * بررسی آیا IP در لیست CIDR هاست
     */
    private static function isIpInCidrs(string $ip, array $cidrs): bool
    {
        $ipLong = ip2long($ip);
        
        if ($ipLong === false) {
            return false;
        }

        foreach ($cidrs as $cidr) {
            if (strpos($cidr, '/') === false) {
                continue;
            }
            
            [$subnet, $mask] = explode('/', $cidr, 2);
            $subnetLong = ip2long($subnet);
            $maskLong = -1 << (32 - (int)$mask);

            if (($ipLong & $maskLong) === ($subnetLong & $maskLong)) {
                return true;
            }
        }

        return false;
    }

    /**
     * رد درخواست و لاگ خطا
     */
    private static function fail(string $message, ?Logger $logger = null): void
    {
        if ($logger) {
            $logger->warn('Security check failed', ['reason' => $message]);
        }
        
        http_response_code(403);
        echo 'Forbidden';
        throw new \RuntimeException($message);
    }

    /**
     * تولید توکن امنیتی تصادفی
     */
    public static function generateSecretToken(int $length = 32): string
    {
        return bin2hex(random_bytes($length / 2));
    }

    /**
     * هش کردن رمز عبور
     */
    public static function hashPassword(string $password): string
    {
        return password_hash($password, PASSWORD_DEFAULT);
    }

    /**
     * بررسی رمز عبور
     */
    public static function verifyPassword(string $password, string $hash): bool
    {
        return password_verify($password, $hash);
    }

    /**
     * پاکسازی ورودی HTML
     */
    public static function escape(string $input): string
    {
        return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }
}
