<?php

namespace App\Http\Controllers;

use App\Enums\CheckoutPaymentMode;
use App\Enums\CustomFieldType;
use App\Enums\EventStatus;
use App\Enums\OrderStatus;
use App\Enums\TicketIac;
use App\Http\Requests\StoreCheckoutOrderRequest;
use App\Models\Attendee;
use App\Models\Event;
use App\Models\Order;
use App\Models\PlatformFeeSetting;
use App\Models\TicketType;
use App\Services\CheckoutOrderSession;
use App\Services\FeeCalculator;
use App\Services\GuestBuyerAccountService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

/**
 * Flujo público de compra de entradas para un evento publicado.
 *
 * Rutas típicas:
 * - `store`: POST crea una orden `pending`, genera asistentes con `security_code`, guarda `checkout_email`
 *   para invitados y redirige a la pantalla de confirmación/pago.
 * - `confirmation`: GET muestra resumen y botones Stripe/PayPal según configuración de la plataforma.
 *
 * Seguridad:
 * - Solo eventos en estado `published`.
 * - La confirmación exige que la orden pertenezca al evento del slug y que {@see CheckoutOrderSession::canAccess()} autorice.
 *
 * @todo Extraer parte del cálculo de fees y validación por ticket a un servicio si el controlador sigue creciendo.
 */
class CheckoutController extends Controller
{
    /**
     * Crea la orden pendiente y los registros de asistentes.
     *
     * Pasos resumidos:
     * 1. Idempotencia opcional vía `idempotency_key` + caché (evita doble orden por doble clic).
     * 2. Validación de existencia de tipos de entrada y capacidad disponible.
     * 3. Validación por fila de datos de asistente según `TicketIac` y custom fields requeridos.
     * 4. Cálculo de totales con {@see FeeCalculator} (subtotal organizador + cargo plataforma por entrada).
     * 5. Persistencia transaccional de `orders` + `attendees`, sesión de checkout y respuesta JSON o redirect.
     *
     * @return RedirectResponse|JsonResponse Redirección HTML o JSON con `redirect` para el checkout SPA (Alpine/fetch).
     */
    public function store(StoreCheckoutOrderRequest $request, Event $event): RedirectResponse|JsonResponse
    {
        abort_unless($event->status === EventStatus::Published, 404);

        // ── Idempotencia: misma clave + mismo usuario/IP → reutilizar orden ya creada en ventana de 24 h ──
        $validatedForIdem = $request->validated();
        $idempotencyKey = isset($validatedForIdem['idempotency_key']) && is_string($validatedForIdem['idempotency_key'])
            ? $validatedForIdem['idempotency_key']
            : null;
        $cacheKey = $idempotencyKey !== null
            ? 'checkout:'.hash('sha256', $idempotencyKey.':'.($request->user()?->id ?? $request->ip()))
            : null;

        if ($cacheKey !== null && Cache::has($cacheKey)) {
            $existingOrderId = (int) Cache::get($cacheKey);
            $existingOrder = Order::find($existingOrderId);
            if ($existingOrder) {
                CheckoutOrderSession::grant($request, $existingOrder);
                $redirectUrl = route('landing.event.checkout.confirmation', [
                    'event' => $event->slug,
                    'checkoutOrder' => $existingOrder->uuid,
                ]);

                return ($request->wantsJson() || $request->ajax())
                    ? response()->json(['redirect' => $redirectUrl])
                    : redirect()->to($redirectUrl);
            }
        }

        $payload = $request->attendeesPayload();
        if ($payload === []) {
            return $this->errorResponse($request, [
                'attendees' => [__('Debes seleccionar al menos una entrada.')],
            ]);
        }

        $ticketIds = array_keys($payload);
        $tickets = TicketType::query()
            ->where('event_id', $event->id)
            ->whereIn('id', $ticketIds)
            ->where('is_active', true)
            ->with('customFields')
            ->get()
            ->keyBy('id');

        if ($tickets->count() !== count($ticketIds)) {
            return $this->errorResponse($request, [
                'attendees' => [__('Una de las entradas seleccionadas ya no está disponible.')],
            ]);
        }

        // ── Validación por ticket: stock IAC (individual attendee collection) y custom fields obligatorios ──
        $errors = [];
        $subtotal = 0.0;

        foreach ($payload as $ticketId => $rows) {
            /** @var TicketType $ticket */
            $ticket = $tickets[$ticketId];
            $qty = count($rows);

            if ($qty < 1) {
                $errors["attendees.$ticketId"][] = __('Cantidad inválida.');

                continue;
            }

            $cap = $ticket->effectiveCapacity();
            if ($cap !== null) {
                $available = max(0, $cap - (int) $ticket->quantity_sold);
                if ($qty > $available) {
                    $errors["attendees.$ticketId"][] = __('No hay suficientes entradas disponibles para :name.', [
                        'name' => $ticket->name,
                    ]);

                    continue;
                }
            }

            $iac = $ticket->iac instanceof TicketIac ? $ticket->iac : TicketIac::tryFrom((string) $ticket->iac) ?? TicketIac::Required;
            $needsAttendeeData = $iac !== TicketIac::None;

            foreach ($rows as $idx => $row) {
                $attendeeKey = "attendees.$ticketId.$idx";

                if ($needsAttendeeData) {
                    if ($row['holder_name'] === '') {
                        $errors["$attendeeKey.holder_name"][] = __('El nombre del asistente es obligatorio.');
                    }
                    if ($row['holder_email'] === '') {
                        $errors["$attendeeKey.holder_email"][] = __('El correo electrónico del asistente es obligatorio.');
                    }
                }

                foreach ($ticket->customFields as $cf) {
                    if (! $cf->required) {
                        continue;
                    }
                    $value = $row['meta'][$cf->slug] ?? null;
                    if (! $this->customFieldHasValue($cf->type, $value)) {
                        $errors["$attendeeKey.meta.{$cf->slug}"][] = __('El campo ":label" es obligatorio.', [
                            'label' => $cf->label,
                        ]);
                    }
                }
            }

            $price = (float) ($ticket->sale_price ?? $ticket->price);
            $subtotal += $price * $qty;
        }

        if ($errors !== []) {
            return $this->errorResponse($request, $errors);
        }

        // ── Fee total plataforma = suma por entrada de (fijo + porcentaje sobre precio base del ticket) ──
        $baseSubtotal = $subtotal;
        $totalPlatformFee = 0.0;

        foreach ($payload as $ticketId => $rows) {
            $ticket = $tickets[$ticketId];
            $basePrice = (float) ($ticket->sale_price ?? $ticket->price);
            $qty = count($rows);

            $ticketFee = FeeCalculator::fixedFee() + ($basePrice * FeeCalculator::percentageFee());
            $totalPlatformFee += round($ticketFee * $qty, 2);
        }

        $finalTotalPaidByUser = round($baseSubtotal + $totalPlatformFee, 2);

        $validated = $request->validated();
        $checkoutEmail = isset($validated['checkout_email']) && is_string($validated['checkout_email']) && $validated['checkout_email'] !== ''
            ? $validated['checkout_email']
            : null;

        $order = DB::transaction(function () use ($event, $payload, $tickets, $finalTotalPaidByUser, $totalPlatformFee, $baseSubtotal, $request, $checkoutEmail) {
            $order = Order::create([
                'user_id' => $request->user()?->id,
                'checkout_email' => $checkoutEmail,
                'organizer_id' => $event->organizer_id,
                'total_amount' => $finalTotalPaidByUser,
                'platform_fee' => $totalPlatformFee,
                'net_organizer_amount' => $baseSubtotal,
                'status' => OrderStatus::Pending->value,
            ]);

            foreach ($payload as $ticketId => $rows) {
                /** @var TicketType $ticket */
                $ticket = $tickets[$ticketId];
                foreach ($rows as $row) {
                    Attendee::create([
                        'order_id' => $order->id,
                        'ticket_type_id' => $ticket->id,
                        'organizer_id' => $event->organizer_id,
                        'holder_name' => $row['holder_name'] ?: null,
                        'holder_email' => $row['holder_email'] ?: null,
                        'registration_data' => $row['meta'] ?? [],
                    ]);
                }
            }

            $event->touch();

            return $order;
        });

        if ($cacheKey !== null) {
            Cache::put($cacheKey, $order->id, now()->addHours(24));
        }

        CheckoutOrderSession::grant($request, $order);

        $redirectUrl = route('landing.event.checkout.confirmation', [
            'event' => $event->slug,
            'checkoutOrder' => $order->uuid,
        ]);

        if ($request->wantsJson() || $request->ajax()) {
            return response()->json([
                'redirect' => $redirectUrl,
            ], 200);
        }

        return redirect()->to($redirectUrl);
    }

    /**
     * Pantalla post-checkout: resumen de orden y opciones de pago (Stripe/PayPal) según llaves y modo configurados.
     *
     * Reparación de datos legacy: si la orden ya está pagada pero aún sin `user_id` (despliegues anteriores),
     * intenta enlazar/crear cuenta comprador y enviar correo de bienvenida con contraseña (solo si aplica).
     */
    public function confirmation(Request $request, Event $event, Order $checkoutOrder, GuestBuyerAccountService $guestBuyerAccount): View
    {
        abort_unless($event->status === EventStatus::Published, 404);

        $checkoutOrder->load(['attendees.ticketType']);

        // Evita abrir confirmación de otra carrera pegando UUID en la URL.
        $hasTicketsForEvent = $checkoutOrder->attendees->contains(fn ($a) => (int) $a->ticketType?->event_id === (int) $event->id);
        abort_unless($hasTicketsForEvent, 404);

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

        CheckoutOrderSession::grant($request, $checkoutOrder);

        if ($checkoutOrder->status === OrderStatus::Completed && $checkoutOrder->user_id === null) {
            $created = $guestBuyerAccount->ensureClienteAccountLinked($checkoutOrder);
            $checkoutOrder->refresh();
            if ($created) {
                $email = $guestBuyerAccount->resolveBuyerEmail($checkoutOrder);
                if ($email !== '') {
                    $guestBuyerAccount->queuePasswordSetupEmail($email);
                }
            }
        }

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

        $stripeKeysOk = is_string(config('services.stripe.key')) && trim((string) config('services.stripe.key')) !== '';
        $paypalKeysOk = is_string(config('services.paypal.client_id')) && trim((string) config('services.paypal.client_id')) !== '';

        $showStripeCheckout = $stripeKeysOk
            && ($mode === CheckoutPaymentMode::Both || $mode === CheckoutPaymentMode::StripeOnly);
        $showPaypalCheckout = $paypalKeysOk
            && ($mode === CheckoutPaymentMode::Both || $mode === CheckoutPaymentMode::PaypalOnly);

        return view('landing.checkout-confirmation', [
            'event' => $event,
            'order' => $checkoutOrder,
            'showStripeCheckout' => $showStripeCheckout,
            'showPaypalCheckout' => $showPaypalCheckout,
        ]);
    }

    /**
     * Respuesta unificada de error: JSON para peticiones AJAX o redirect con errores de sesión para formulario clásico.
     *
     * @param  array<string, array<int, string>>  $errors  Claves alineadas con nombres de campos del request.
     */
    private function errorResponse(Request $request, array $errors): RedirectResponse|JsonResponse
    {
        if ($request->wantsJson() || $request->ajax()) {
            throw ValidationException::withMessages($errors);
        }

        $validator = Validator::make([], []);
        foreach ($errors as $key => $messages) {
            foreach ((array) $messages as $msg) {
                $validator->errors()->add($key, $msg);
            }
        }

        return redirect()->back()->withErrors($validator)->withInput();
    }

    /**
     * Determina si un valor enviado cumple “campo relleno” según el tipo de custom field (checkbox vs texto).
     */
    private function customFieldHasValue(?CustomFieldType $type, mixed $value): bool
    {
        if ($value === null) {
            return false;
        }

        return match ($type) {
            CustomFieldType::Checkbox => filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === true,
            default => is_string($value) ? trim($value) !== '' : ! empty($value),
        };
    }
}
