<?php

namespace App\Http\Controllers\Webhooks;

use App\Enums\TransactionGateway;
use App\Models\Order;
use App\Models\PaymentWebhookEvent;
use App\Services\AuditLogger;
use App\Services\OrderPaymentService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Http;

/**
 * Endpoint HTTP para notificaciones de PayPal (capturas completadas / denegadas).
 *
 * Opcionalmente verifica firma con `verify-webhook-signature` si existe `PAYPAL_WEBHOOK_ID`.
 * Asocia la orden mediante `custom_id` guardado al crear la orden de checkout.
 */
final class PaypalWebhookController
{
    public function __construct(
        private readonly OrderPaymentService $orderPaymentService,
        private readonly AuditLogger $auditLogger,
    ) {}

    /** Recibe el JSON del webhook; idempotencia por `payment_webhook_events`. */
    public function __invoke(Request $request): Response
    {
        $eventId = (string) $request->input('id', '');
        $payload = $request->all();

        if ($eventId === '') {
            return response('bad request', 400);
        }

        $row = PaymentWebhookEvent::query()->firstOrCreate(
            [
                'gateway' => TransactionGateway::Paypal->value,
                'event_id' => $eventId,
            ],
            [
                'payload' => $payload,
            ]
        );

        if ($row->processed_at !== null) {
            return response('ok', 200);
        }

        $this->auditLogger->log(
            action: 'payment.webhook_received',
            module: 'payments',
            summary: 'PayPal webhook '.((string) ($payload['event_type'] ?? 'unknown')),
            context: ['event_id' => $eventId, 'type' => $payload['event_type'] ?? null],
            subject: null,
            request: $request,
        );

        // Verificación (si está configurada). En sandbox/local puede no estar lista aún.
        try {
            $this->verifyWebhookSignatureIfConfigured($request, $payload);
        } catch (\Throwable $e) {
            $row->forceFill(['error_message' => 'signature_verification_failed: '.$e->getMessage()])->save();

            // 200 para evitar reintentos en dev; el error queda logueado.
            return response('ok', 200);
        }

        try {
            $this->processPaypalEvent($payload);
            $row->forceFill(['processed_at' => now(), 'error_message' => null])->save();
        } catch (\Throwable $e) {
            $row->forceFill(['error_message' => $e->getMessage()])->save();
        }

        return response('ok', 200);
    }

    /**
     * Verifica firma usando `verify-webhook-signature` cuando hay `PAYPAL_WEBHOOK_ID`.
     *
     * @param  array<string, mixed>  $payload
     */
    private function verifyWebhookSignatureIfConfigured(Request $request, array $payload): void
    {
        $webhookId = (string) env('PAYPAL_WEBHOOK_ID');
        if ($webhookId === '') {
            return;
        }

        $token = $this->paypalAccessToken();
        $base = $this->paypalBaseUrl();

        $resp = Http::withToken($token)->post($base.'/v1/notifications/verify-webhook-signature', [
            'auth_algo' => $request->header('PAYPAL-AUTH-ALGO'),
            'cert_url' => $request->header('PAYPAL-CERT-URL'),
            'transmission_id' => $request->header('PAYPAL-TRANSMISSION-ID'),
            'transmission_sig' => $request->header('PAYPAL-TRANSMISSION-SIG'),
            'transmission_time' => $request->header('PAYPAL-TRANSMISSION-TIME'),
            'webhook_id' => $webhookId,
            'webhook_event' => $payload,
        ])->throw();

        $status = (string) ($resp->json('verification_status') ?? '');
        if ($status !== 'SUCCESS') {
            throw new \RuntimeException('verification_status='.$status);
        }
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function processPaypalEvent(array $payload): void
    {
        $type = (string) ($payload['event_type'] ?? '');
        if (! in_array($type, ['PAYMENT.CAPTURE.COMPLETED', 'PAYMENT.CAPTURE.DENIED'], true)) {
            return;
        }

        $resource = is_array($payload['resource'] ?? null) ? $payload['resource'] : [];
        $referenceId = (string) ($resource['id'] ?? '');

        // Intentamos recuperar order_id desde custom_id (lo seteamos al crear la orden).
        $orderId = (int) ($resource['custom_id'] ?? 0);
        if ($orderId <= 0) {
            $orderId = (int) ($resource['supplementary_data']['related_ids']['custom_id'] ?? 0);
        }

        if ($orderId <= 0 || $referenceId === '') {
            return;
        }

        $order = Order::query()->find($orderId);
        if (! $order) {
            return;
        }

        if ($type === 'PAYMENT.CAPTURE.COMPLETED') {
            $amount = isset($resource['amount']['value']) ? (float) $resource['amount']['value'] : null;
            $currency = isset($resource['amount']['currency_code']) ? (string) $resource['amount']['currency_code'] : null;

            $this->orderPaymentService->completeOrder(
                $order,
                TransactionGateway::Paypal,
                $referenceId,
                $payload,
                amount: $amount,
                currency: $currency,
            );
        } else {
            $this->orderPaymentService->failTransaction(
                $order,
                TransactionGateway::Paypal,
                $referenceId,
                errorCode: 'paypal_capture_denied',
                errorMessage: 'PAYMENT.CAPTURE.DENIED',
                payload: $payload,
            );
        }
    }

    private function paypalBaseUrl(): string
    {
        $mode = config('services.paypal.mode', 'sandbox');

        return $mode === 'live'
            ? 'https://api-m.paypal.com'
            : 'https://api-m.sandbox.paypal.com';
    }

    private function paypalAccessToken(): string
    {
        $clientId = (string) config('services.paypal.client_id');
        $secret = (string) config('services.paypal.secret');

        $resp = Http::asForm()
            ->withBasicAuth($clientId, $secret)
            ->post($this->paypalBaseUrl().'/v1/oauth2/token', [
                'grant_type' => 'client_credentials',
            ])
            ->throw();

        return (string) $resp->json('access_token');
    }
}
