<?php

namespace App\Http\Controllers;

use App\Enums\CheckoutPaymentMode;
use App\Enums\EventStatus;
use App\Enums\OrderStatus;
use App\Enums\TransactionGateway;
use App\Enums\TransactionStatus;
use App\Models\Event;
use App\Models\Order;
use App\Models\PlatformFeeSetting;
use App\Models\Transaction;
use App\Services\CheckoutOrderSession;
use App\Services\OrderPaymentService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\ValidationException;
use Stripe\StripeClient;

/**
 * Endpoints JSON invocados desde la página de confirmación de checkout (Stripe Elements / PayPal JS).
 *
 * Responsabilidades:
 * - Crear PaymentIntent de Stripe Connect con `application_fee_amount` (comisión plataforma) y transferencia al `stripe_account_id` del organizador.
 * - Crear y capturar órdenes REST de PayPal vinculadas a `orders.id` en `custom_id`.
 * - Registrar filas `transactions` en estado pendiente y delegar el cobro exitoso a {@see OrderPaymentService}.
 *
 * Seguridad: {@see authorizeCheckoutPayment()} exige sesión de checkout válida u orden del mismo usuario.
 */
final class CheckoutPaymentController extends Controller
{
    public function __construct(
        private readonly OrderPaymentService $orderPaymentService,
    ) {}

    /**
     * Devuelve `client_secret` para inicializar Stripe.js y registra transacción pendiente por PaymentIntent.
     */
    public function createStripePaymentIntent(Request $request, Event $event, Order $checkoutOrder): JsonResponse
    {
        $this->authorizeCheckoutPayment($request, $event, $checkoutOrder);
        $this->assertStripeCheckoutAllowed();

        $checkoutOrder->load(['attendees.ticketType.event.organizer']);
        $organizer = $checkoutOrder->attendees->first()?->ticketType?->event?->organizer;
        $destinationAccount = $organizer?->stripe_account_id;

        if (! is_string($destinationAccount) || trim($destinationAccount) === '') {
            throw ValidationException::withMessages([
                'payment' => [__('Este organizador aún no tiene Stripe Connect configurado.')],
            ]);
        }

        $grandTotal = (float) $checkoutOrder->total_amount;
        $amountCents = (int) round($grandTotal * 100);
        $feeCents = (int) round(((float) $checkoutOrder->platform_fee) * 100);
        $currency = strtolower($checkoutOrder->currency ?? 'usd');

        $stripe = new StripeClient(config('services.stripe.secret'));
        $intent = $stripe->paymentIntents->create([
            'amount' => $amountCents,
            'currency' => $currency,
            'automatic_payment_methods' => ['enabled' => true],
            'metadata' => [
                'order_id' => (string) $checkoutOrder->id,
                'event_id' => (string) $event->id,
            ],
            'application_fee_amount' => $feeCents,
            'transfer_data' => [
                'destination' => $destinationAccount,
            ],
        ]);

        Transaction::query()->updateOrCreate(
            ['gateway' => TransactionGateway::Stripe->value, 'reference_id' => $intent->id],
            [
                'order_id' => $checkoutOrder->id,
                'organizer_id' => $checkoutOrder->organizer_id,
                'status' => TransactionStatus::Pending->value,
                'amount' => $grandTotal,
                'currency' => strtoupper($currency),
                'payload' => ['type' => 'payment_intent_created'],
            ]
        );

        return response()->json([
            'client_secret' => $intent->client_secret,
            'payment_intent_id' => $intent->id,
            'publishable_key' => config('services.stripe.key'),
        ]);
    }

    /**
     * Crea orden de captura en PayPal y devuelve id para el SDK del lado cliente.
     */
    public function createPaypalOrder(Request $request, Event $event, Order $checkoutOrder): JsonResponse
    {
        $this->authorizeCheckoutPayment($request, $event, $checkoutOrder);
        $this->assertPaypalCheckoutAllowed();

        $grandTotal = (float) $checkoutOrder->total_amount;
        $currency = strtoupper($checkoutOrder->currency ?? 'USD');

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

        $resp = Http::withToken($token)
            ->post($base.'/v2/checkout/orders', [
                'intent' => 'CAPTURE',
                'purchase_units' => [
                    [
                        'custom_id' => (string) $checkoutOrder->id,
                        'description' => $event->title,
                        'amount' => [
                            'currency_code' => $currency,
                            'value' => number_format($grandTotal, 2, '.', ''),
                        ],
                    ],
                ],
                'application_context' => [
                    'brand_name' => config('app.name'),
                    'user_action' => 'PAY_NOW',
                ],
            ])
            ->throw();

        $data = $resp->json();
        $paypalOrderId = (string) ($data['id'] ?? '');
        if ($paypalOrderId === '') {
            throw ValidationException::withMessages([
                'payment' => [__('No se pudo crear la orden de PayPal.')],
            ]);
        }

        Transaction::query()->updateOrCreate(
            ['gateway' => TransactionGateway::Paypal->value, 'reference_id' => $paypalOrderId],
            [
                'order_id' => $checkoutOrder->id,
                'organizer_id' => $checkoutOrder->organizer_id,
                'status' => TransactionStatus::Pending->value,
                'amount' => $grandTotal,
                'currency' => $currency,
                'payload' => $data,
            ]
        );

        return response()->json([
            'paypal_order_id' => $paypalOrderId,
            'client_id' => config('services.paypal.client_id'),
        ]);
    }

    /**
     * Ejecuta capture en PayPal; si el estado es COMPLETED completa la orden vía {@see OrderPaymentService::completeOrder()}.
     */
    public function capturePaypalOrder(Request $request, Event $event, Order $checkoutOrder): JsonResponse
    {
        $this->authorizeCheckoutPayment($request, $event, $checkoutOrder);
        $this->assertPaypalCheckoutAllowed();

        $paypalOrderId = (string) $request->input('paypal_order_id');
        if ($paypalOrderId === '') {
            throw ValidationException::withMessages([
                'paypal_order_id' => [__('Falta el id de la orden de PayPal.')],
            ]);
        }

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

        $resp = Http::withToken($token)
            ->post($base.'/v2/checkout/orders/'.$paypalOrderId.'/capture', (object) [])
            ->throw();

        $data = $resp->json();
        $status = (string) ($data['status'] ?? '');

        if ($status === 'COMPLETED') {
            $grandTotal = (float) $checkoutOrder->total_amount;
            $this->orderPaymentService->completeOrder(
                $checkoutOrder,
                TransactionGateway::Paypal,
                $paypalOrderId,
                $data,
                amount: $grandTotal,
                currency: strtoupper($checkoutOrder->currency ?? 'USD')
            );

            return response()->json(['ok' => true]);
        }

        $this->orderPaymentService->failTransaction(
            $checkoutOrder,
            TransactionGateway::Paypal,
            $paypalOrderId,
            errorCode: 'paypal_capture_not_completed',
            errorMessage: 'PayPal capture status: '.$status,
            payload: $data,
        );

        return response()->json([
            'ok' => false,
            'status' => $status,
        ], 422);
    }

    /** Lanza validación HTTP si Stripe no está habilitado por configuración de fees/modos de checkout. */
    private function assertStripeCheckoutAllowed(): void
    {
        if (! $this->stripeCheckoutAvailable()) {
            throw ValidationException::withMessages([
                'payment' => [__('El pago con tarjeta no está disponible.')],
            ]);
        }
    }

    /** Lanza validación HTTP si PayPal no está habilitado en la configuración actual. */
    private function assertPaypalCheckoutAllowed(): void
    {
        if (! $this->paypalCheckoutAvailable()) {
            throw ValidationException::withMessages([
                'payment' => [__('PayPal no está disponible para este checkout.')],
            ]);
        }
    }

    /** Comprueba llaves Stripe y modo de checkout (`PlatformFeeSetting`). */
    private function stripeCheckoutAvailable(): bool
    {
        $key = config('services.stripe.key');
        $secret = config('services.stripe.secret');
        if (! is_string($key) || trim($key) === '' || ! is_string($secret) || trim($secret) === '') {
            return false;
        }

        $mode = PlatformFeeSetting::current()->checkout_payment_mode;

        return $mode === CheckoutPaymentMode::Both || $mode === CheckoutPaymentMode::StripeOnly;
    }

    /** Comprueba credenciales PayPal y modo de checkout. */
    private function paypalCheckoutAvailable(): bool
    {
        $clientId = config('services.paypal.client_id');
        $secret = config('services.paypal.secret');
        if (! is_string($clientId) || trim($clientId) === '' || ! is_string($secret) || trim($secret) === '') {
            return false;
        }

        $mode = PlatformFeeSetting::current()->checkout_payment_mode;

        return $mode === CheckoutPaymentMode::Both || $mode === CheckoutPaymentMode::PaypalOnly;
    }

    /**
     * Comprueba evento publicado, orden pendiente del mismo evento y permiso de sesión/usuario.
     */
    private function authorizeCheckoutPayment(Request $request, Event $event, Order $checkoutOrder): void
    {
        abort_unless($event->status === EventStatus::Published, 404);

        $checkoutOrder->loadMissing('attendees.ticketType');
        $hasTicketsForEvent = $checkoutOrder->attendees->contains(fn ($a) => (int) $a->ticketType?->event_id === (int) $event->id);
        abort_unless($hasTicketsForEvent, 404);

        abort_unless($checkoutOrder->status === OrderStatus::Pending, 422);

        abort_unless(CheckoutOrderSession::canAccess($request, $checkoutOrder), 403);
    }

    /** URL API PayPal según `services.paypal.mode` (sandbox vs live). */
    private function paypalBaseUrl(): string
    {
        $mode = config('services.paypal.mode', 'sandbox');

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

    /** OAuth2 client_credentials para llamadas server-side a PayPal. */
    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();

        $json = $resp->json();
        $token = (string) ($json['access_token'] ?? '');
        if ($token === '') {
            throw ValidationException::withMessages([
                'payment' => [__('No se pudo autenticar con PayPal.')],
            ]);
        }

        return $token;
    }
}
