<?php

namespace Modules\Payment\Http\Controllers;

use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Modules\Payment\Actions\ConfirmPaymentAction;
use Modules\Payment\Enums\PaymentMethod;
use Modules\Payment\Exceptions\InvalidWebhookSignatureException;
use Modules\Payment\Factories\PaymentGatewayFactory;

/**
 * One inbound webhook route for every gateway, routed by PaymentMethod and
 * resolved through PaymentGatewayFactory (T5.6) — mirrors bnf_event's
 * `OrderController::paymentComplete`/`{method}` dispatch, but through the
 * factory instead of a switch, so adding a gateway needs no controller
 * change (domain.md §6).
 */
class PaymentWebhookController extends Controller
{
    public function __construct(
        private PaymentGatewayFactory $gateways,
        private ConfirmPaymentAction $confirmPaymentAction,
    ) {}

    public function handle(Request $request, PaymentMethod $method, ?string $encryptBookingId = null): Response
    {
        $payload = $request->all();

        // Optional, mirrors bnf_event's `{encryptOrderId?}` — a redundant,
        // signature-independent way to locate the booking directly from the
        // URL (used by ConfirmPaymentAction, T5.10) alongside whatever
        // order id the gateway's own signed payload carries. Never fatal if
        // it's missing or fails to decrypt; the signature check is what
        // actually authenticates this request.
        $bookingId = $this->decryptBookingId($encryptBookingId);

        try {
            $result = $this->gateways->make($method)->handleWebhook($payload);
        } catch (InvalidWebhookSignatureException $exception) {
            // Raw payload persisted regardless of outcome (domain.md §6).
            Log::warning('Payment webhook rejected: invalid signature', [
                'gateway' => $method->value,
                'booking_id' => $bookingId,
                'payload' => $payload,
            ]);

            throw $exception;
        }

        Log::info('Payment webhook received', [
            'gateway' => $method->value,
            'booking_id' => $bookingId,
            'status' => $result->status->value,
            'gateway_transaction_id' => $result->gatewayTransactionId,
            'payload' => $payload,
        ]);

        $payment = $result->gatewayTransactionId !== null
            ? $this->confirmPaymentAction->handle($method, $result->gatewayTransactionId)
            : null;

        if ($payment === null) {
            Log::warning('Payment webhook has no matching payment to confirm', [
                'gateway' => $method->value,
                'gateway_transaction_id' => $result->gatewayTransactionId,
            ]);
        }

        // KBZ retries any delivery that doesn't get back this exact literal
        // body — we acknowledge regardless of whether a matching payment
        // was found, since retrying won't fix that mismatch.
        return response('success', 200);
    }

    private function decryptBookingId(?string $encryptBookingId): ?int
    {
        if ($encryptBookingId === null) {
            return null;
        }

        try {
            return (int) Crypt::decryptString($encryptBookingId);
        } catch (DecryptException) {
            return null;
        }
    }
}
