<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Twilio\Security\RequestValidator;

/**
 * Verifies X-Twilio-Signature on inbound webhook requests.
 * See https://www.twilio.com/docs/usage/webhooks/webhooks-security#validating-signatures-from-twilio
 *
 * Bypassed with a warning in non-production environments when
 * services.twilio.token is not configured.
 *
 * Uses the official Twilio SDK RequestValidator which handles URL normalisation
 * (e.g. stripping default ports) exactly as Twilio does when computing signatures.
 */
class VerifyTwilioWebhook
{
    public function handle(Request $request, Closure $next): Response
    {
        $authToken = config('services.twilio.token');

        if (empty($authToken)) {
            if (app()->environment('production')) {
                logger()->error('Twilio webhook received but TWILIO_AUTH_TOKEN is not configured.');
                return response('Twilio auth token not configured.', 500);
            }
            logger()->warning('Twilio webhook signature skipped (no token configured).');
            return $next($request);
        }

        $signature = $request->header('X-Twilio-Signature');
        if (! $signature) {
            logger()->warning('Twilio webhook received without X-Twilio-Signature header.', [
                'url' => $request->fullUrl(),
            ]);
            return response('Missing Twilio signature header.', 403);
        }

        // Twilio signs the exact URL it requested, preserving the original
        // query-string order. Laravel's fullUrl() pipes the query through
        // Symfony's normalizeQueryString(), which ksorts and RFC3986-re-encodes
        // the params — so the reconstructed URL no longer matches what Twilio
        // signed for any webhook carrying a query string (e.g. the call-transfer
        // redirect: ?to_agent_id=..&from_agent_id=..). That mismatch fails
        // validation, returns 403, and Twilio plays "an application error has
        // occurred" to the caller. Rebuild the URL from the raw query string.
        $url      = $this->stripQuery($request->fullUrl());
        $rawQuery = $request->server('QUERY_STRING');
        if (is_string($rawQuery) && $rawQuery !== '') {
            $url .= '?' . $rawQuery;
        }

        $params = $request->post();

        $validator = new RequestValidator($authToken);

        if (! $validator->validate($signature, $url, $params)) {
            logger()->warning('Twilio webhook signature validation failed.', [
                'url'       => $url,
                'signature' => $signature,
                'params'    => array_keys($params),
            ]);
            return response('Invalid Twilio webhook signature.', 403);
        }

        return $next($request);
    }

    /**
     * Drop the query string from a URL, leaving scheme, host and path intact.
     */
    protected function stripQuery(string $url): string
    {
        $pos = strpos($url, '?');

        return $pos === false ? $url : substr($url, 0, $pos);
    }
}
