<?php

namespace App\Services;

use Firebase\JWT\JWT;
use Illuminate\Http\Request;

class ApiJwt
{
    /**
     * Symmetric key for HS256.
     *
     * @return string
     */
    public static function signingKey()
    {
        $key = env('JWT_SECRET');
        if (!empty($key)) {
            return $key;
        }
        $appKey = config('app.key', '');
        if (is_string($appKey) && strpos($appKey, 'base64:') === 0) {
            return base64_decode(substr($appKey, 7));
        }

        return (string) $appKey;
    }

    /**
     * Access JWT: carries DB access token (app) or memId (web) for ApiToken middleware.
     *
     * @param string $accessToken
     * @param string $resourceType
     * @param int    $userId
     * @param string $appType
     * @return string
     */
    public static function encodeAccess($accessToken, $resourceType, $userId, $appType)
    {
        $now = time();
        $ttl = (int) config('jwt.access_ttl', 3600);
        $payload = array(
            'iat' => $now,
            'exp' => $now + $ttl,
            'token_use' => 'access',
            'resourceType' => $resourceType,
            'app' => $appType,
        );
        if ($resourceType === 'web') {
            $payload['memId'] = (int) $userId;
        } else {
            $payload['accessToken'] = $accessToken;
        }

        return JWT::encode($payload, self::signingKey(), 'HS256');
    }

    /**
     * Refresh JWT: carries opaque refresh token for /v2019/jwt/refresh.
     *
     * @param string $refreshToken
     * @param int    $userId
     * @param string $appType
     * @return string
     */
    public static function encodeRefresh($refreshToken, $userId, $appType)
    {
        $now = time();
        $ttl = (int) config('jwt.refresh_ttl', 5184000);
        $payload = array(
            'iat' => $now,
            'exp' => $now + $ttl,
            'token_use' => 'refresh',
            'refresh' => $refreshToken,
            'uid' => (int) $userId,
            'app' => $appType,
        );

        return JWT::encode($payload, self::signingKey(), 'HS256');
    }

    /**
     * Decode a refresh JWT or return null.
     *
     * @param string $jwt
     * @return object|null
     */
    public static function decodeRefreshToken($jwt)
    {
        try {
            $decoded = JWT::decode($jwt, self::signingKey(), array('HS256'));
        } catch (\Exception $e) {
            return null;
        }
        if (!isset($decoded->token_use) || $decoded->token_use !== 'refresh') {
            return null;
        }
        if (empty($decoded->refresh)) {
            return null;
        }

        return $decoded;
    }

    /**
     * If Authorization: Bearer &lt;access JWT&gt; or body/query param `jwt` is present, merge claims
     * into the request so controllers see accessToken / memId / resourceType / jwt_app.
     * `jwt_app` mirrors JWT claim `app` (SM|CM) so authToken() uses the same appType as login.
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse|null
     */
    public static function mergeBearerIntoRequest(Request $request)
    {
        $jwtString = self::extractAccessJwtString($request);
        
        if ($jwtString === null || $jwtString === '') {
            return null;
        }
        try {
            $decoded = JWT::decode($jwtString, self::signingKey(), array('HS256'));
        } catch (\Exception $e) {
            return \App\Helper::invalidAuthentication();
        }
        if (!isset($decoded->token_use) || $decoded->token_use !== 'access') {
            return \App\Helper::invalidAuthentication();
        }
        
        $merge = array();
        if (isset($decoded->accessToken)) {
            $merge['accessToken'] = (string) $decoded->accessToken;
        }
        if (isset($decoded->at)) {
            $merge['accessToken'] = (string) $decoded->at;
        }
        if (isset($decoded->memId)) {
            $merge['memId'] = $decoded->memId;
            $merge['memid'] = $decoded->memId;
        }
        if (isset($decoded->mid)) {
            $merge['memId'] = $decoded->mid;
            $merge['memid'] = $decoded->mid;
        }
        if (isset($decoded->resourceType)) {
            $merge['resourceType'] = $decoded->resourceType;
        }
        if (isset($decoded->typ)) {
            $merge['resourceType'] = $decoded->typ;
        }
        if (isset($decoded->app)) {
            $merge['jwt_app'] = (string) $decoded->app;
        }
        if (!empty($merge)) {
            $request->merge($merge);
        }

        return null;
    }

    /**
     * Authorization value when Apache/CGI strips HTTP Authorization (common on shared hosting).
     * See: SetEnvIf / RewriteRule passing HTTP_AUTHORIZATION, or use body param `jwt`.
     *
     * @param Request $request
     * @return string|null
     */
    public static function getAuthorizationHeaderValue(Request $request)
    {
        
        $auth = $request->header('Authorization');
        if ($auth !== null && $auth !== '') {
            return $auth;
        }
        if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
            return $_SERVER['HTTP_AUTHORIZATION'];
        }
        if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
            return $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
        }
        // Apache rewrites may nest: REDIRECT_REDIRECT_HTTP_AUTHORIZATION, etc.
        foreach ($_SERVER as $key => $value) {
            if (!is_string($value) || $value === '') {
                continue;
            }
            if (substr($key, -strlen('HTTP_AUTHORIZATION')) === 'HTTP_AUTHORIZATION') {
                return $value;
            }
        }
        if (function_exists('apache_request_headers')) {
            $headers = apache_request_headers();
            if (isset($headers['Authorization'])) {
                return $headers['Authorization'];
            }
            if (isset($headers['authorization'])) {
                return $headers['authorization'];
            }
        }
        $xAuth = $request->header('X-Authorization');
        if ($xAuth !== null && $xAuth !== '') {
            return $xAuth;
        }

        return null;
    }

    /**
     * Raw JWT from Authorization: Bearer … or `jwt` (JSON body / query / form).
     *
     * @param Request $request
     * @return string|null
     */
    public static function extractAccessJwtString(Request $request)
    {
        $auth = self::getAuthorizationHeaderValue($request);
        
        if ($auth !== null && $auth !== '') {
            if (stripos($auth, 'Bearer ') === 0) {
                $t = trim(substr($auth, 7));
                if ($t !== '') {
                    return $t;
                }
            }
        }
        $xToken = $request->header('X-Access-Token');
        if ($xToken !== null && $xToken !== '') {
            $t = trim($xToken);
            if ($t !== '') {
                return $t;
            }
        }
        $fromInput = $request->input('jwt');
        if ($fromInput === null || $fromInput === '') {
            return null;
        }
        if (!is_string($fromInput)) {
            return null;
        }
        $t = trim($fromInput);
        if (stripos($t, 'Bearer ') === 0) {
            $t = trim(substr($t, 7));
        }

        return $t !== '' ? $t : null;
    }
}
