<?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/intaleq-api/.secret_key'));
        $this->hmacSecret = getenv('SECRET_KEY_HMAC') ?: '';
        $this->fpPepper   = getenv('FP_PEPPER') ?: '';
        $this->issuer     = getenv('APP_ISSUER') ;
        $this->redis      = $redis;
    }

    // ── توليد 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;
        }

        $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);
        }

        return JWT::encode($payload, $this->secretKey, self::ALGO);
    }

    // ── توليد 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/intaleq-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
        if (($decoded->iss ?? '') !== $this->issuer) {
            self::abort(401, 'Invalid token issuer');
        }

        // 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;

            if ($fpInToken === null || $fpHeader === null) {
                error_log("[SECURITY] Fingerprint missing | user: $userId");
                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 — مطلوب للعمليات الحساسة (Wallet/Logout)
        $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') ?: '';

            // التوقيع يضم الـ Body + Timestamp + Nonce لمنع التكرار والتلاعب
            $payloadToSign = $body . $timestamp . $nonce;
            $expectedHmac  = hash_hmac('sha256', $payloadToSign, $this->hmacSecret);

            if (!hash_equals($expectedHmac, $hmacHeader)) {
                error_log("[SECURITY] HMAC mismatch | user: $userId | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '?'));
                self::abort(403, 'Invalid HMAC signature');
            }
        }

        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 = '/home/intaleq-api/.internal_socket_key';
        $sent    = $_SERVER['HTTP_X_INTERNAL_KEY'] ?? '';
        $expected = (file_exists($keyPath) ? trim(file_get_contents($keyPath)) : '') ?: 'Intaleq_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;
        }
    }

    private static function abort(int $code, string $message): never
    {
        http_response_code($code);
        echo json_encode(['error' => $message]);
        exit;
    }
}
