<?php

namespace Modular\Connector\Http\Middleware;

use Modular\Connector\Helper\OauthClient;
use Modular\ConnectorDependencies\Ares\Framework\Foundation\Auth\JWT;
use Modular\ConnectorDependencies\Illuminate\Http\Request;
use Modular\ConnectorDependencies\Illuminate\Support\Facades\Log;
use function Modular\ConnectorDependencies\abort;
use function Modular\ConnectorDependencies\app;

class AuthenticateLoopback
{

    /**
     * Handle an incoming request.
     *
     * @param Request $request
     * @param \Closure $next
     * @return mixed
     * @throws \Exception
     */
    public function handle($request, \Closure $next)
    {
        $action = app()->getScheduleHook();

        // Check for x-mo-Authentication header or sig parameter (API authentication with client_secret)
        if ($request->hasHeader('x-mo-authentication') || $request->has('sig')) {
            $token = $request->hasHeader('x-mo-authentication')
                ? $request->header('x-mo-authentication', '')
                : $request->get('sig');

            $source = $request->hasHeader('x-mo-authentication') ? 'header' : 'parameter';

            // Verify JWT with client_secret
            if (!$this->verifyApiAuthentication($request, $token, $source)) {
                abort(404);
            }

            Log::debug("API authentication verified successfully via {$source}");
        } elseif ($request->hasHeader('Authentication')) {
            // Legacy Authentication header (uses default hashing key)
            $authHeader = $request->header('Authentication', '');

            if (!JWT::verify($authHeader, $action)) {
                Log::debug('Invalid JWT for schedule hook', [
                    'hook' => $action,
                    'header' => $authHeader,
                ]);

                abort(404);
            }
        } else {
            // Fallback to nonce validation
            $isValid = check_ajax_referer($action, 'nonce', false);

            if (!$isValid) {
                Log::debug('Invalid nonce for schedule hook', [
                    'hook' => $action,
                    'nonce' => $request->input('nonce'),
                ]);

                abort(404);
            }
        }

        Log::debug('Valid authentication for loopback request');

        return $next($request);
    }

    /**
     * Verify API authentication with client_secret.
     *
     * @param Request $request
     * @param string $token JWT token from header or parameter
     * @param string $source 'header' or 'parameter'
     * @return bool
     */
    private function verifyApiAuthentication($request, $token, $source)
    {
        // Verify token is not empty
        if (empty($token)) {
            Log::debug("API auth ({$source}): Token is empty");
            return false;
        }

        // Get client_secret from OAuth client
        $clientSecret = OauthClient::getClient()->getClientSecret();

        // If client_secret is empty, authentication is invalid
        if (empty($clientSecret)) {
            Log::debug("API auth ({$source}): client_secret is empty");
            return false;
        }

        // Get request data based on route
        $requestData = $this->getRequestData($request);

        if ($requestData === null) {
            Log::debug("API auth ({$source}): Unable to get request data");
            return false;
        }

        // Verify JWT using client_secret with request data
        if (!$this->verifyJwtWithData($token, $clientSecret, $requestData)) {
            Log::debug("API auth ({$source}): Invalid JWT");
            return false;
        }

        return true;
    }

    /**
     * Get request data based on current route.
     *
     * @param Request $request
     * @return object|array|null
     */
    private function getRequestData($request)
    {
        $routeName = $request->route()->getName();

        // Special handling for OAuth route
        if ($routeName === 'modular-connector.oauth') {
            return (object)[
                'code' => $request->get('code'),
                'state' => $request->get('state'),
            ];
        }
        // For other routes, get modularRequest from cache
        $modularRequest = $request->route()->parameter('modular_request');

        if (!$modularRequest) {
            Log::debug('x-mo-Authentication: modularRequest not found in cache', [
                'route' => $routeName,
            ]);
            return null;
        }

        // Validate modularRequest structure
        $requiredKeys = [
            'id',
            'request_id',
            'type',
            'body',
            'site_id',
            'created_at',
            'updated_at',
            'deleted_at',
            'status',
            'expired_at',
        ];

        /**
         * @var \Modular\SDK\Objects\SiteRequest $modularRequest
         */
        foreach ($requiredKeys as $key) {
            if (!array_key_exists($key, $modularRequest->attributesToArray())) {
                Log::debug('x-mo-Authentication: modularRequest missing required key', [
                    'missing_key' => $key,
                    'route' => $routeName,
                ]);

                return null;
            }
        }

        // Return the entire modularRequest object for hashing
        return $modularRequest;
    }

    /**
     * Verify JWT signature and integrity.
     *
     * Decodes the JWT, regenerates the signature with the same payload and client_secret,
     * and compares. If signatures match, the JWT is authentic and unmodified.
     *
     * @param string $token
     * @param string $clientSecret
     * @param object|array $requestData
     * @return bool
     */
    private function verifyJwtWithData($token, $clientSecret, $requestData)
    {
        // Remove Bearer prefix
        $token = str_replace('Bearer ', '', $token);
        $jwtParts = explode('.', $token);

        if (count($jwtParts) !== 3) {
            Log::debug('x-mo-Authentication: Invalid JWT structure');
            return false;
        }

        [$base64UrlHeader, $base64UrlPayload, $base64UrlSignature] = $jwtParts;

        // Decode header and payload to inspect them
        $header = json_decode(JWT::base64UrlDecode($base64UrlHeader));
        $payload = json_decode(JWT::base64UrlDecode($base64UrlPayload));

        if (!$header || !$payload) {
            Log::debug('x-mo-Authentication: Failed to decode JWT', [
                'header_valid' => is_object($header),
                'payload_valid' => is_object($payload),
            ]);
            return false;
        }

        Log::debug('x-mo-Authentication: JWT payload decoded', [
            'client_id' => $payload->client_id ?? null,
            'exp' => $payload->exp ?? null,
            'iat' => $payload->iat ?? null,
        ]);

        // Regenerate signature with the exact same payload
        // If this matches the received signature, the payload is authentic and unmodified
        $signatureInput = "$base64UrlHeader.$base64UrlPayload";
        $expectedSignature = hash_hmac('sha256', $signatureInput, $clientSecret, true);
        $receivedSignature = JWT::base64UrlDecode($base64UrlSignature);

        if (!hash_equals($expectedSignature, $receivedSignature)) {
            Log::debug('x-mo-Authentication: JWT signature mismatch - token tampered or wrong secret');
            return false;
        }

        Log::debug('x-mo-Authentication: JWT signature valid');

        // Verify expiration
        $currentTime = time();
        if ($currentTime > ($payload->exp ?? 0)) {
            Log::debug('x-mo-Authentication: JWT expired', [
                'exp' => $payload->exp,
                'now' => $currentTime,
            ]);
            return false;
        }

        // Verify issued at
        if ($currentTime < ($payload->iat ?? 0)) {
            Log::debug('x-mo-Authentication: JWT not yet valid', [
                'iat' => $payload->iat,
                'now' => $currentTime,
            ]);
            return false;
        }

        return true;
    }
}
