<?php
// ============================================================
// core/Auth/JwtService.php
// JWT آمن: JTI + Blacklist في Redis + Refresh Token
// ============================================================

use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Firebase\JWT\BeforeValidException;

class JwtService
{
    private string $secretKey;
    private string $hmacSecret;
    private string $fpPepper;
    private ?Redis $redis;

    private string $issuer;
    private const ACCESS_TTL    = 3600;     // 1 ساعة
    private const REFRESH_TTL   = 2592000; // 30 يوم
    private const ALGO          = 'HS256';

    // Endpoints مسموح لها بتوكن نوع registration
    private const REGISTRATION_ENDPOINTS = [
        'loginFirstTime', 'loginFirstTimeDriver',
        'checkPhoneNumberISVerfiedDriver', 'checkPhoneNumberISVerfiedPassenger',
        'otpmessage', 'signup', 'verifyEmail', 'verifyOtpMessage',
        'sendVerifyEmail', 'sendWhatsAppDriver', 'register_passenger',
        'sendWhatsOpt', 'verifyOtp', 'auth_proxy', 'addToken',
        'loginFromGoogle', 'loginUsingCredentialsWithoutGoogle',
        'register', 'sendOtpMessageDriver', 'getTokensPassenger',
        'send_otp', 'verify_otp', 'errorApp', 'register_driver',
    ];

    public function __construct(?Redis $redis = null)
    {
        $this->secretKey  = trim(file_get_contents('/home/siro-api/.secret_key'));
        $this->hmacSecret = getenv('SECRET_KEY_HMAC') ?: '';
        $this->fpPepper   = getenv('FP_PEPPER') ?: '';
        $this->issuer     = (string)(getenv('APP_ISSUER') ?: '');
        $this->redis      = $redis;
        
        // Debugging fpPepper
        if (empty($this->fpPepper)) {
            error_log("[JWT_DEBUG] fpPepper is EMPTY in constructor");
        } else {
            error_log("[JWT_DEBUG] fpPepper is SET (length: " . strlen($this->fpPepper) . ")");
        }
    }


    // ── توليد Access Token ──────────────────────────────────
    public function generateAccessToken(
        int|string $userId,
        string $role,
        string $audience,
        ?string $fingerprint = null
    ): string {
        $jti = bin2hex(random_bytes(16));

        $ttl = 3600;
        if ($role === 'driver') {
            $ttl = 14400;
        } elseif ($role === 'passenger') {
            $ttl = 3600;
        } elseif ($role === 'service') {
            $ttl = 14400; // 4 hours as requested
        }

        $payload = [
            'iss'        => $this->issuer,
            'aud'        => $audience,
            'user_id'    => $userId,
            'role'       => $role,
            'token_type' => 'access',
            'jti'        => $jti,
            'iat'        => time(),
            'exp'        => time() + $ttl,
        ];

        if ($fingerprint && $this->fpPepper) {
            $payload['fingerPrint'] = hash('sha256', $fingerprint . $this->fpPepper);
        }

        $token = JWT::encode($payload, $this->secretKey, self::ALGO);

        // تخزين في Redis لضمان عدم التكرار وإمكانية الإلغاء
        if ($this->redis) {
            $this->redis->setex("active_jti:{$userId}", $ttl, $jti);
            $this->redis->setex("active_token:{$userId}:{$audience}", $ttl, $token);
        }

        return $token;
    }

    // ── فك تشفير التوكن للتحقق الداخلي ────────────────────────
    public function decodeToken(string $token): ?object
    {
        try {
            return JWT::decode($token, new Key($this->secretKey, self::ALGO));
        } catch (Exception $e) {
            return null;
        }
    }


    // ── توليد Refresh Token ─────────────────────────────────
    public function generateRefreshToken(int|string $userId): array
    {
        $token = bin2hex(random_bytes(32));
        $exp   = time() + self::REFRESH_TTL;

        // تخزين في Redis
        if ($this->redis) {
            $this->redis->setex(
                "refresh:{$userId}:{$token}",
                self::REFRESH_TTL,
                json_encode(['user_id' => $userId, 'created_at' => time()])
            );
        }

        return ['token' => $token, 'expires_at' => $exp];
    }

    // ── التحقق الكامل من التوكن ────────────────────────────
    public function authenticate(): object
    {
        // 1. استخراج التوكن
        $authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
        $token      = null;
        if (preg_match('/Bearer\s(\S+)/', $authHeader, $m)) {
            $token = $m[1];
        }

        if (!$token) {
            self::abort(401, 'Authorization token required');
        }

        // 2. Decode
        try {
            $decoded = JWT::decode($token, new Key($this->secretKey, self::ALGO));
        } catch (ExpiredException $e) {
            self::abort(401, 'Token expired');
        } catch (SignatureInvalidException $e) {
            // محاولة فك التشفير بمفتاح المحفظة (Wallet secret fallback)
            $payKeyPath = '/home/siro-api/.secret_key_pay';
            $payKey = file_exists($payKeyPath) ? trim(file_get_contents($payKeyPath)) : '';
            
            if ($payKey) {
                try {
                    $decoded = JWT::decode($token, new Key($payKey, self::ALGO));
                } catch (Exception $e2) {
                    self::abort(401, 'Invalid token signature');
                }
            } else {
                self::abort(401, 'Invalid token signature');
            }
        } catch (BeforeValidException $e) {
            self::abort(401, 'Token not yet valid');
        } catch (Exception $e) {
            self::abort(401, 'Invalid token');
        }

        // 3. Issuer (Only check if configured)
        if (!empty($this->issuer) && ($decoded->iss ?? '') !== $this->issuer) {
            self::abort(401, 'Invalid token issuer: expected ' . $this->issuer . ' but got ' . ($decoded->iss ?? 'none'));
        }

        // 3.1 App Signature Verification (Service Only)
        $role = $decoded->role ?? 'unknown';
        if ($role === 'service') {
            $appSignature = $_SERVER['HTTP_X_APP_SIGNATURE'] ?? null;
            if ($appSignature === null && function_exists('getallheaders')) {
                $headers = array_change_key_case(getallheaders(), CASE_LOWER);
                $appSignature = $headers['x-app-signature'] ?? null;
            }

            // نقبل بصمة الـ Release أو الـ Debug
            $allowedSignatures = array_filter([
                getenv('APP_SIGNATURE_SERVICE_RELEASE'),
                getenv('APP_SIGNATURE_SERVICE_DEBUG'),
                getenv('APP_SIGNATURE_HASH') // Fallback
            ]);

            if (!empty($allowedSignatures)) {
                if ($appSignature === null || !in_array($appSignature, $allowedSignatures)) {
                    error_log("[SECURITY_ERROR] App Signature Mismatch! Role: $role | Got: " . ($appSignature ?? 'NONE') . " | User: " . ($decoded->user_id ?? 'unknown'));
                    self::abort(403, 'App integrity check failed. Please use the official app.');
                }
            }
        }

        // 4. User ID
        $userId = $decoded->user_id ?? $decoded->sub ?? null;
        if (!$userId) {
            self::abort(401, 'Invalid JWT payload');
        }

        // 5. JTI Blacklist (تحقق من توكنات ملغاة)
        $jti = $decoded->jti ?? null;
        if ($jti && $this->redis) {
            if ($this->redis->exists("jwt:blacklist:$jti")) {
                self::abort(401, 'Token has been revoked');
            }
        }

        // 6. token_type — قيّد registration endpoints
        $tokenType = $decoded->token_type ?? 'access';
        if ($tokenType === 'registration' || $tokenType === 'new') {
            $currentFile = basename($_SERVER['PHP_SELF'], '.php');
            $allowed = false;
            foreach (self::REGISTRATION_ENDPOINTS as $ep) {
                if (strcasecmp($currentFile, $ep) === 0) {
                    $allowed = true;
                    break;
                }
            }
            if (!$allowed) {
                error_log("[SECURITY] Registration token blocked on: $currentFile | user: $userId");
                self::abort(403, 'Token not authorized for this action');
            }
        }

        // 7. Device Fingerprint (إلزامي للـ Access Tokens)
        if ($this->fpPepper && $tokenType === 'access') {
            $fpInToken = $decoded->fingerPrint ?? null;
            $fpHeader  = $_SERVER['HTTP_X_DEVICE_FP'] ?? null;
            
            // محاولة جلب الهيدر بطرق بديلة إذا لم يوجد في $_SERVER
            if ($fpHeader === null && function_exists('getallheaders')) {
                $headers = array_change_key_case(getallheaders(), CASE_LOWER);
                $fpHeader = $headers['x-device-fp'] ?? null;
            }

            if ($fpInToken === null || $fpHeader === null) {
                $allHeaders = json_encode(getallheaders());
                error_log("[SECURITY] Fingerprint missing | user: $userId | fpInToken: " . ($fpInToken ?? 'NULL') . " | fpHeader: " . ($fpHeader ?? 'NULL') . " | Headers: $allHeaders");
                self::abort(403, 'Device verification required');
            }

            $expected = hash('sha256', $fpHeader . $this->fpPepper);
            if (!hash_equals($expected, $fpInToken)) {
                error_log("[SECURITY] Device mismatch | user: $userId | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '?'));
                self::abort(403, 'Device mismatch');
            }
        }

        // 8. HMAC Verification (Derived Secret for Service)
        $hmacHeader = $_SERVER['HTTP_X_HMAC_AUTH'] ?? null;
        if ($hmacHeader !== null) {
            $timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
            $nonce     = $_SERVER['HTTP_X_NONCE'] ?? '';
            $body      = file_get_contents('php://input') ?: '';

            // اشتقاق مفتاح الـ HMAC الخاص بهذا المستخدم
            $userSecret = hash_hmac('sha256', (string)$userId, $this->hmacSecret);
            
            // المعادلة الموحدة: Body + Timestamp + Nonce
            $payloadToSign = $body . $timestamp . $nonce;
            $expectedHmac  = hash_hmac('sha256', $payloadToSign, $userSecret);

            if (!hash_equals($expectedHmac, $hmacHeader)) {
                $debugMsg = "User: $userId | Expected: $expectedHmac | Got: $hmacHeader | DerivedSecret: $userSecret | MasterSecret(4): " . substr($this->hmacSecret, 0, 4) . " | Body($bodyLen): '$body' | TS: '$timestamp' | Nonce: '$nonce'";
                $bodyLen = strlen($body);
                error_log("[SECURITY] HMAC mismatch | " . $debugMsg);
                // TEMPORARY: expose debug in response for diagnosis
                http_response_code(403);
                echo json_encode(['error' => 'HMAC_DEBUG', 'debug' => $debugMsg]);
                exit;
            }
        }

        return $decoded;
    }

    // ── إلغاء توكن (Logout / Password Change) ──────────────
    public function revokeToken(string $jti, int $remainingTTL = 900): void
    {
        if ($this->redis && $jti) {
            $this->redis->setex("jwt:blacklist:$jti", $remainingTTL + 60, '1');
        }
    }

    // ── Internal API Key — للـ get_connect.php ─────────────
    public static function validateInternalKey(): void
    {
        $keyPath = getenv('INTERNAL_SOCKET_KEY_PATH');
        $sent    = $_SERVER['HTTP_X_INTERNAL_KEY'] ?? '';
        $expected = (file_exists($keyPath) ? trim(file_get_contents($keyPath)) : '') ?: 'Siro_Secure_Bridge_Key_2026_@!socket';

        if (!$expected || !hash_equals($expected, $sent)) {
            error_log('[SECURITY] Invalid internal key from: ' . ($_SERVER['REMOTE_ADDR'] ?? '?'));
            http_response_code(403);
            echo json_encode(['error' => 'Unauthorized internal request']);
            exit;
        }
    }

    public function getFpPepper(): string
    {
        return $this->fpPepper;
    }

    private static function abort(int $code, string $message)
    {
        error_log("[JWT_AUTH_FAILED] Code: $code | Message: $message | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '?') . " | URI: " . ($_SERVER['REQUEST_URI'] ?? '?'));
        http_response_code($code);
        echo json_encode(['error' => $message]);
        exit;
    }
}
