<?php

namespace App\Http\Controllers;

use App\Exceptions\InsufficientStockException;
use App\Exceptions\PaymentGatewayException;
use App\Exceptions\StockException;
use App\Http\Requests\StoreAddressRequest;
use App\Http\Requests\VerifyRazorpayRequest;
use App\Models\Address;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Product;
use App\Models\Setting;
use App\Models\ShippingMethod;
use App\Models\State;
use App\Models\StockMovement;
use App\Models\VariantStockMovement;
use App\Services\AddressCityResolver;
use App\Models\Location;
use App\Services\CartService;
use App\Services\CouponService;
use App\Services\InventoryService;
use App\Services\LocationInventoryService;
use App\Services\OrderAllocation\OrderAllocationService;
use App\Services\OrderPlacementNotificationService;
use App\Services\ProfitService;
use App\Services\RazorpayService;
use App\Support\PaymentLogger;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;

class CheckoutController extends Controller
{
    public function __construct(
        protected CartService $cartService,
        protected RazorpayService $razorpayService,
        protected CouponService $couponService,
        protected InventoryService $inventoryService,
        protected LocationInventoryService $locationInventory,
        protected OrderAllocationService $allocationService,
        protected ProfitService $profitService,
        protected AddressCityResolver $cityResolver,
    ) {
        $this->middleware('auth');
    }

    public function index(Request $request): View|RedirectResponse
    {
        $cart = $this->getCart($request);

        if ($cart->items->isEmpty()) {
            return redirect()->route('cart.index')->with('error', 'Your cart is empty.');
        }

        // Load variant relationships for stock validation
        $cart->load('items.product');

        // Validate stock availability for all cart items
        $stockIssues = [];
        $lines = [];
        foreach ($cart->items as $item) {
            // Unified model: product is the actual sellable item
            $product = $item->product;
            $availableStock = $this->locationInventory->useLocationInventory()
                ? $this->locationInventory->getAvailableStock($product->id)
                : $product->current_stock;
            $itemName = $product->name;
            $lines[] = ['product_id' => (int) $product->id, 'qty' => (int) $item->qty];
            
            // Check if product is active
            if (!$product->is_active) {
                $stockIssues[] = "{$itemName} is no longer available";
                continue;
            }
            
            // Check stock availability
            if ($availableStock <= 0) {
                $stockIssues[] = "{$itemName} is out of stock (You have {$item->qty} in cart)";
            } elseif ($availableStock < $item->qty) {
                $stockIssues[] = "{$itemName} - Only {$availableStock} available (You have {$item->qty} in cart)";
            }
        }

        // If there are stock issues, redirect back to cart with error messages
        if (!empty($stockIssues)) {
            $errorMessage = "Cannot proceed to checkout:\n" . implode("\n", array_slice($stockIssues, 0, 5));
            if (count($stockIssues) > 5) {
                $errorMessage .= "\n... and " . (count($stockIssues) - 5) . " more issue(s)";
            }
            
            return redirect()->route('cart.index')
                ->with('error', $errorMessage)
                ->with('stock_issues', $stockIssues);
        }

        $maySplitShipment = false;
        if ($this->locationInventory->useLocationInventory() && !empty($lines)) {
            $maySplitShipment = $this->allocationService->wouldRequireSplit($lines, 'home_delivery');
        }

        $addresses = auth()->user()
            ->addresses()
            ->with(['country', 'stateRelation', 'cityRelation'])
            ->orderByDesc('is_default')
            ->orderByDesc('id')
            ->get();
        
        // Get active shipping methods (with fallback if table doesn't exist)
        try {
            $shippingMethods = ShippingMethod::active()->ordered()->get();
        } catch (\Exception $e) {
            // Fallback to empty collection if table doesn't exist
            $shippingMethods = \Illuminate\Support\Collection::make([]);
        }
        
        // Get payment settings
        $razorpayEnabled = Setting::get('payment_razorpay_enabled', '1') === '1';
        $codEnabled = Setting::get('payment_cod_enabled', '1') === '1';
        $pickupStores = Location::query()->stores()->allowsPickup()->orderBy('priority')->get();

        return view('checkout.index', compact(
            'cart',
            'addresses',
            'shippingMethods',
            'razorpayEnabled',
            'codEnabled',
            'pickupStores',
            'maySplitShipment'
        ));
    }

    public function storeAddress(StoreAddressRequest $request): RedirectResponse
    {
        $data = $request->validated();
        $data['user_id'] = auth()->id();

        $state = State::find($data['state_id']);
        $city = $this->cityResolver->resolve(
            cityId: $data['city_id'] ?? null,
            customName: $data['city_custom'] ?? null,
            stateId: (int) $data['state_id'],
            countryId: (int) $data['country_id'],
            userId: auth()->id(),
        );

        $data['state'] = $state?->name;
        $data['city'] = $city->name;
        $data['city_id'] = $city->id;
        unset($data['city_custom']);

        if ($data['is_default'] ?? false) {
            Address::where('user_id', auth()->id())->update(['is_default' => false]);
        }

        Address::create($data);

        return redirect()->route('checkout.index')->with('success', 'Address added successfully!');
    }

    public function updateAddress(StoreAddressRequest $request, Address $address): RedirectResponse
    {
        abort_unless($address->user_id === auth()->id(), 403);

        $data = $request->validated();

        $state = State::find($data['state_id']);
        $city = $this->cityResolver->resolve(
            cityId: $data['city_id'] ?? null,
            customName: $data['city_custom'] ?? null,
            stateId: (int) $data['state_id'],
            countryId: (int) $data['country_id'],
            userId: auth()->id(),
        );

        $data['state'] = $state?->name;
        $data['city'] = $city->name;
        $data['city_id'] = $city->id;
        unset($data['city_custom']);

        if ($data['is_default'] ?? false) {
            Address::where('user_id', auth()->id())
                ->where('id', '!=', $address->id)
                ->update(['is_default' => false]);
        }

        $address->update($data);

        return redirect()->route('checkout.index')->with('success', 'Address updated successfully!');
    }

    public function createRazorpayOrder(Request $request): JsonResponse
    {
        // Get valid shipping method codes for validation
        try {
            $validShippingCodes = ShippingMethod::active()->pluck('code')->toArray();
        } catch (\Exception $e) {
            $validShippingCodes = [];
        }

        $fulfillment = $this->validateCheckoutFulfillment($request, $validShippingCodes);
        if ($fulfillment instanceof JsonResponse) {
            return $fulfillment;
        }
        $fulfillmentType = $fulfillment['fulfillment_type'];

        $cart = $this->getCart($request);

        if ($cart->items->isEmpty()) {
            return response()->json(['error' => 'Cart is empty'], 400);
        }

        // Ensure cart totals are calculated (subtotal and discount)
        $this->cartService->recalculateTotals($cart);
        $cart->refresh();

        $subtotal = $cart->totals['subtotal'] ?? 0;
        $shippingPrice = $fulfillmentType === 'pickup'
            ? 0.0
            : $this->resolveShippingPrice($request->shipping_method, $subtotal);

        // Recalculate totals with the selected shipping method price
        $tax = $cart->totals['tax'] ?? 0;
        $discount = $cart->totals['discount'] ?? 0;
        // Since prices are tax-inclusive, subtotal already includes tax
        // Total = Subtotal - Discount + Shipping (tax already included in subtotal)
        $total = $subtotal - $discount + $shippingPrice;

        // Update cart totals with correct shipping
        $cart->update([
            'totals' => [
                'subtotal' => round($subtotal, 2),
                'tax' => round($tax, 2),
                'discount' => round($discount, 2),
                'shipping' => round($shippingPrice, 2),
                'total' => round($total, 2),
            ],
        ]);
        $cart->refresh();

        // Validate that totals exist and are properly calculated
        if (!$cart->totals || !isset($cart->totals['total']) || $cart->totals['total'] <= 0) {
            return response()->json(['error' => 'Cart totals could not be calculated'], 400);
        }

        try {
            $addressSnapshot = $this->buildCheckoutAddressSnapshot($request, $fulfillmentType);
        } catch (\InvalidArgumentException $e) {
            return response()->json(['error' => $e->getMessage()], 422);
        }

        try {
            DB::beginTransaction();

            // Clean up old failed orders first
            $this->cleanupOldFailedOrders(auth()->id());

            // Priority 1: Find ANY recent failed order (within 1 hour) - always reuse it
            // Note: Failed orders now have items, so we don't check for empty items
            $failedOrder = Order::where('user_id', auth()->id())
                ->where('status', Order::STATUS_AWAITING_PAYMENT)
                ->where('payment_status', Order::PAYMENT_FAILED)
                ->where('payment_method', 'online')
                ->where('created_at', '>=', now()->subHour())
                ->orderBy('created_at', 'desc')
                ->first();

            $order = null;
            if ($failedOrder) {
                // Always reuse failed orders - update with current cart totals and address
                $failedOrder->update([
                    'address_snapshot' => $addressSnapshot,
                    'coupon_id' => $cart->coupon_id,
                    'subtotal' => $cart->totals['subtotal'] ?? 0,
                    'discount_total' => $cart->totals['discount'] ?? 0,
                    'tax_total' => 0, // Will be calculated by ProfitService
                    'shipping_total' => $cart->totals['shipping'] ?? 0,
                    'total_payable' => $cart->totals['total'] ?? 0, // Will be recalculated after tax
                    'payment_status' => Order::PAYMENT_PENDING,
                    'payment_method' => 'online',
                    'status' => Order::STATUS_AWAITING_PAYMENT,
                    'razorpay_order_id' => null, // Clear old Razorpay order ID
                    'razorpay_payment_id' => null,
                    'razorpay_signature' => null,
                ]);

                // Delete old order items since cart might have changed
                $failedOrder->items()->delete();

                // Release old stock reservations if any
                $this->inventoryService->releaseByOrderId($failedOrder->id);

                $order = $failedOrder;
            } else {
                // Priority 2: Check for pending orders with stricter matching
                // Note: Pending orders now have items, so we don't check for empty items
                $pendingOrder = Order::where('user_id', auth()->id())
                    ->where('status', Order::STATUS_AWAITING_PAYMENT)
                    ->where('payment_status', Order::PAYMENT_PENDING)
                    ->where('payment_method', 'online')
                    ->where('created_at', '>=', now()->subHour())
                    ->orderBy('created_at', 'desc')
                    ->first();

                if ($pendingOrder && $this->orderMatchesCart($pendingOrder, $cart, $request->address_id, $shippingPrice)) {
                    // Reuse pending order - update with current cart totals and address
                    $pendingOrder->update([
                        'address_snapshot' => $addressSnapshot,
                        'coupon_id' => $cart->coupon_id,
                        'subtotal' => $cart->totals['subtotal'] ?? 0,
                        'discount_total' => $cart->totals['discount'] ?? 0,
                        'tax_total' => $cart->totals['tax'] ?? 0,
                        'shipping_total' => $cart->totals['shipping'] ?? 0,
                        'total_payable' => $cart->totals['total'] ?? 0,
                        'payment_status' => Order::PAYMENT_PENDING,
                        'payment_method' => 'online',
                        'status' => Order::STATUS_AWAITING_PAYMENT,
                        'razorpay_order_id' => null, // Clear old Razorpay order ID
                        'razorpay_payment_id' => null,
                        'razorpay_signature' => null,
                    ]);

                    // Delete old order items since cart might have changed
                    $pendingOrder->items()->delete();

                    // Release old stock reservations if any
                    $this->inventoryService->releaseByOrderId($pendingOrder->id);

                    $order = $pendingOrder;
                } else {
                // Create new order record
                $order = Order::create([
                'user_id' => auth()->id(),
                'source' => 'online',
                'address_snapshot' => $addressSnapshot,
                'coupon_id' => $cart->coupon_id,
                'subtotal' => $cart->totals['subtotal'] ?? 0,
                'discount_total' => $cart->totals['discount'] ?? 0,
                'tax_total' => $cart->totals['tax'] ?? 0,
                'shipping_total' => $cart->totals['shipping'] ?? 0,
                'total_payable' => $cart->totals['total'] ?? 0,
                'payment_status' => Order::PAYMENT_PENDING,
                'payment_method' => 'online', // Razorpay payment
                'status' => Order::STATUS_AWAITING_PAYMENT,
                ]);
                }
            }

            // Allocate to store/warehouse and reserve location-aware stock
            $lines = $cart->items->map(fn ($item) => [
                'product_id' => (int) $item->product_id,
                'qty' => (int) $item->qty,
            ])->values()->all();

            $fulfillmentType = $request->input('fulfillment_type', 'home_delivery');
            $preferredStoreId = $request->input('preferred_store_id');
            if ($preferredStoreId) {
                $order->update(['preferred_store_id' => $preferredStoreId]);
            }

            try {
                if ($this->locationInventory->useLocationInventory()) {
                    $pincode = $addressSnapshot['pin'] ?? null;
                    // Start in parity mode (default store) unless multi-candidate scoring finds stock
                    $this->allocationService->allocateAndReserve(
                        $order->fresh(),
                        $lines,
                        $fulfillmentType === 'pickup' ? 'pickup' : 'home_delivery',
                        $pincode,
                        null,
                        null,
                        true // allowSplit when Split Orders rule is active
                    );
                } else {
                    foreach ($lines as $line) {
                        $this->inventoryService->reserve($line['product_id'], $line['qty'], null, $order->id);
                    }
                }
            } catch (InsufficientStockException|StockException $e) {
                DB::rollBack();
                $this->locationInventory->useLocationInventory()
                    ? $this->locationInventory->releaseByOrderId($order->id)
                    : $this->inventoryService->releaseByOrderId($order->id);
                $order->delete();

                return response()->json(['error' => $e->getMessage()], 400);
            }

            // Create order items from cart (but don't confirm stock or create stock movements yet)
            // This ensures failed orders have items for reference
            if ($order->items()->count() === 0) {
                $cartItems = [];
                foreach ($cart->items as $cartItem) {
                    // Prepare cart item data for profit service
                    $cartItems[] = [
                        'product_id' => $cartItem->product_id,
                        'qty' => $cartItem->qty,
                        'price' => $cartItem->price_at_add,
                    ];
                }

                // Use ProfitService to create order items with profit calculations and tax calculation
                $this->profitService->allocateOrderCosts($order, $cartItems);
            }
            
            // Refresh order to get calculated tax_total
            $order->refresh();
            
            // Recalculate total_payable
            // Prices are tax-inclusive, so total = subtotal - discount + shipping
            $order->update([
                'total_payable' => $order->subtotal - $order->discount_total + $order->shipping_total
            ]);

            DB::commit();

            // Keep external gateway call out of DB transaction to avoid holding locks under network latency.
            try {
                $razorpayOrder = $this->razorpayService->createOrder(
                    $order->total_payable,
                    'order_' . $order->id
                );

                $order->update([
                    'razorpay_order_id' => $razorpayOrder['id'],
                ]);
            } catch (\Exception $e) {
                $this->inventoryService->releaseByOrderId($order->id, true);
                $order->update([
                    'payment_status' => Order::PAYMENT_FAILED,
                    'status' => Order::STATUS_CANCELLED,
                ]);

                Log::error('Checkout Razorpay initialization failed after order creation', [
                    'order_id' => $order->id,
                    'user_id' => auth()->id(),
                    'error' => $e->getMessage(),
                ]);

                return response()->json([
                    'error' => 'Failed to initialize payment. Please retry.'
                ], 500);
            }

            return response()->json([
                'success' => true,
                'order_id' => $order->id,
                'razorpay_order_id' => $razorpayOrder['id'],
                'amount' => $razorpayOrder['amount'],
                'currency' => $razorpayOrder['currency'],
                'key' => \App\Models\Setting::get('payment_razorpay_key_id') ?: config('services.razorpay.key'),
            ]);
        } catch (InsufficientStockException $e) {
            DB::rollBack();
            // Release any reservations if order was created
            if (isset($order)) {
                $this->inventoryService->releaseByOrderId($order->id);
                $order->delete();
            }

            Log::warning('Checkout failed: Insufficient stock', [
                'user_id' => auth()->id(),
                'error' => $e->getMessage(),
                'context' => $e->getContext(),
            ]);

            return response()->json([
                'error' => $e->getMessage()
            ], 400);
        } catch (StockException $e) {
            DB::rollBack();
            // Release any reservations if order was created
            if (isset($order)) {
                $this->inventoryService->releaseByOrderId($order->id);
                $order->delete();
            }

            Log::error('Checkout failed: Stock error', [
                'user_id' => auth()->id(),
                'error' => $e->getMessage(),
                'context' => $e->getContext(),
            ]);

            return response()->json([
                'error' => $e->getMessage()
            ], 400);
        } catch (\Exception $e) {
            DB::rollBack();
            // Release any reservations if order was created
            if (isset($order)) {
                $this->inventoryService->releaseByOrderId($order->id);
                $order->delete();
            }

            Log::error('Checkout order creation failed', [
                'user_id' => auth()->id(),
                'cart_id' => $cart->id ?? null,
                'cart_totals' => $cart->totals ?? null,
                'cart_items_count' => $cart->items->count() ?? 0,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
            ]);

            return response()->json([
                'error' => 'Failed to create order: ' . $e->getMessage()
            ], 500);
        } catch (\Throwable $e) {
            DB::rollBack();
            if (isset($order)) {
                $this->inventoryService->releaseByOrderId($order->id);
                $order->delete();
            }

            Log::critical('Checkout order creation fatal error', [
                'user_id' => auth()->id(),
                'cart_id' => $cart->id ?? null,
                'error_class' => get_class($e),
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
            ]);

            return response()->json([
                'error' => 'Failed to create order due to a server error. Please retry once.'
            ], 500);
        }
    }

    public function verifyRazorpay(VerifyRazorpayRequest $request): JsonResponse
    {
        $data = $request->validated();
        PaymentLogger::info('Razorpay verify request received', [
            'razorpay_order_id' => $data['razorpay_order_id'] ?? null,
            'payment_id' => $data['razorpay_payment_id'] ?? null,
            'user_id' => auth()->id(),
        ]);

        $order = Order::where('razorpay_order_id', $data['razorpay_order_id'])
            ->where('user_id', auth()->id())
            ->first();

        if (!$order) {
            PaymentLogger::warning('Razorpay verify: Order not found for payload', [
                'razorpay_order_id' => $data['razorpay_order_id'] ?? null,
                'payment_id' => $data['razorpay_payment_id'] ?? null,
                'user_id' => auth()->id(),
                'failure_bucket' => 'backend_state_race',
            ]);

            return response()->json([
                'error' => 'Unable to match payment with order. Please refresh and try again.'
            ], 404);
        }

        $verified = $this->razorpayService->verifySignature(
            $data['razorpay_order_id'],
            $data['razorpay_payment_id'],
            $data['razorpay_signature']
        );

        if (!$verified) {
            PaymentLogger::warning('Razorpay verify: Signature verification failed', [
                'order_id' => $order->id,
                'razorpay_order_id' => $data['razorpay_order_id'] ?? null,
                'payment_id' => $data['razorpay_payment_id'] ?? null,
                'failure_bucket' => 'backend_state_race',
            ]);
            $order->update(['payment_status' => Order::PAYMENT_FAILED]);
            // Release stock reservations since payment failed
            $this->inventoryService->releaseByOrderId($order->id, true);

            return response()->json(['error' => 'Payment verification failed'], 400);
        }

        try {
            DB::beginTransaction();

            $order = Order::whereKey($order->id)
                ->lockForUpdate()
                ->firstOrFail();

            // Idempotency check: If order is already paid, skip processing to prevent duplicate stock deduction
            if (
                $order->payment_status === Order::PAYMENT_PAID
                && $order->status === Order::STATUS_PLACED
            ) {
                // Webhook-first success can reach here before normal verify flow.
                // Ensure cart is still cleared for the user.
                $cart = $this->getCart($request);
                $this->cartService->clear($cart);

                PaymentLogger::info('Payment verification skipped: Order already paid', [
                    'order_id' => $order->id,
                    'razorpay_order_id' => $data['razorpay_order_id'],
                    'payment_id' => $data['razorpay_payment_id'] ?? null,
                ]);
                
                DB::commit();
                
                return response()->json([
                    'success' => true,
                    'order_id' => $order->id,
                    'redirect' => route('customer.orders.index'),
                    'message' => 'Payment already processed',
                ]);
            }

            if (
                $order->status !== Order::STATUS_AWAITING_PAYMENT
                || !in_array($order->payment_status, [Order::PAYMENT_PENDING, Order::PAYMENT_FAILED], true)
            ) {
                $previousOrderStatus = $order->status;
                $previousPaymentStatus = $order->payment_status;

                // Recovery path for webhook-first captures: if payment is already captured at Razorpay,
                // sync order to paid/placed instead of failing verification for the customer.
                $gatewayState = $this->razorpayService->resolvePaymentState($data['razorpay_payment_id']);
                if (($gatewayState['status'] ?? RazorpayService::PAYMENT_STATUS_UNKNOWN) === RazorpayService::PAYMENT_STATUS_CAPTURED) {
                    $order->update([
                        'razorpay_payment_id' => $data['razorpay_payment_id'],
                        'razorpay_signature' => $data['razorpay_signature'],
                        'payment_status' => Order::PAYMENT_PAID,
                        'payment_method' => 'online',
                        'status' => Order::STATUS_PLACED,
                        'placed_at' => $order->placed_at ?? now(),
                    ]);

                    // If payment already got captured via webhook, clear cart before returning success.
                    $cart = $this->getCart($request);
                    $this->cartService->clear($cart);

                    PaymentLogger::warning('Razorpay verify: recovered non-payable state using captured payment', [
                        'order_id' => $order->id,
                        'payment_id' => $data['razorpay_payment_id'] ?? null,
                        'razorpay_order_id' => $data['razorpay_order_id'] ?? null,
                        'current_order_status' => $previousOrderStatus,
                        'current_payment_status' => $previousPaymentStatus,
                        'failure_bucket' => 'backend_state_race',
                    ]);

                    DB::commit();

                    return response()->json([
                        'success' => true,
                        'order_id' => $order->id,
                        'redirect' => route('customer.orders.index'),
                        'message' => 'Payment already captured and synced',
                    ]);
                }

                throw new StockException('Order is no longer payable.');
            }

            // Confirm all stock reservations before capture.
            // If this fails, payment must not be captured.
            $reservations = \App\Models\StockReservation::where('order_id', $order->id)
                ->where('status', 'pending')
                ->where('expires_at', '>', now())
                ->get();

            if ($reservations->isEmpty()) {
                $gatewayState = $this->razorpayService->resolvePaymentState($data['razorpay_payment_id']);
                if (($gatewayState['status'] ?? RazorpayService::PAYMENT_STATUS_UNKNOWN) === RazorpayService::PAYMENT_STATUS_CAPTURED) {
                    $order->update([
                        'razorpay_payment_id' => $data['razorpay_payment_id'],
                        'razorpay_signature' => $data['razorpay_signature'],
                        'payment_status' => Order::PAYMENT_PAID,
                        'payment_method' => 'online',
                        'status' => Order::STATUS_PLACED,
                        'placed_at' => $order->placed_at ?? now(),
                    ]);

                    $cart = $this->getCart($request);
                    $this->cartService->clear($cart);

                    PaymentLogger::warning('Razorpay verify: recovered after reservation expiry using captured payment', [
                        'order_id' => $order->id,
                        'payment_id' => $data['razorpay_payment_id'] ?? null,
                        'razorpay_order_id' => $data['razorpay_order_id'] ?? null,
                        'failure_bucket' => 'backend_state_race',
                    ]);

                    DB::commit();

                    return response()->json([
                        'success' => true,
                        'order_id' => $order->id,
                        'redirect' => route('customer.orders.index'),
                        'message' => 'Payment already captured and synced',
                    ]);
                }

                PaymentLogger::warning('Razorpay verify: Stock reservation expired', [
                    'order_id' => $order->id,
                    'razorpay_order_id' => $data['razorpay_order_id'],
                    'payment_id' => $data['razorpay_payment_id'] ?? null,
                    'failure_bucket' => 'backend_state_race',
                ]);
                throw new StockException('Stock reservation expired. Please reorder.');
            }

            if ($this->locationInventory->useLocationInventory()) {
                $locReservations = \App\Models\InventoryReservation::where('order_id', $order->id)
                    ->where('status', 'pending')
                    ->get();
                foreach ($locReservations as $reservation) {
                    $this->locationInventory->confirmReservation($reservation->reservation_token);
                }
            } else {
                foreach ($reservations as $reservation) {
                    $this->inventoryService->confirmReservation($reservation->reservation_token);
                }
            }

            // Capture payment only after stock is safely confirmed.
            $gatewayState = $this->razorpayService->resolvePaymentState($data['razorpay_payment_id']);
            if (($gatewayState['status'] ?? RazorpayService::PAYMENT_STATUS_UNKNOWN) === RazorpayService::PAYMENT_STATUS_CAPTURED) {
                PaymentLogger::info('Razorpay verify: capture skipped, payment already captured (auto-capture)', [
                    'order_id' => $order->id,
                    'payment_id' => $data['razorpay_payment_id'] ?? null,
                    'razorpay_order_id' => $data['razorpay_order_id'] ?? null,
                ]);
            } else {
                try {
                    $this->razorpayService->capturePayment(
                        $data['razorpay_payment_id'],
                        (float) $order->total_payable
                    );
                } catch (PaymentGatewayException $e) {
                    if ($e->reasonCode() !== PaymentGatewayException::CODE_CAPTURE_ALREADY_CAPTURED) {
                        throw $e;
                    }
                    PaymentLogger::info('Razorpay verify: capture skipped, payment already captured', [
                        'order_id' => $order->id,
                        'payment_id' => $data['razorpay_payment_id'] ?? null,
                        'razorpay_order_id' => $data['razorpay_order_id'] ?? null,
                    ]);
                }
            }

            // Update order with payment details after successful capture.
            $order->update([
                'razorpay_payment_id' => $data['razorpay_payment_id'],
                'razorpay_signature' => $data['razorpay_signature'],
                'payment_status' => Order::PAYMENT_PAID,
                'payment_method' => 'online', // Ensure payment method is set
                'status' => Order::STATUS_PLACED,
                'placed_at' => now(),
            ]);
            PaymentLogger::info('Razorpay verify completed successfully', [
                'order_id' => $order->id,
                'payment_id' => $data['razorpay_payment_id'],
                'razorpay_order_id' => $data['razorpay_order_id'],
            ]);

            // Order items should already exist (created when order was created).
            // Stock SALE movements are created solely by confirmReservation above.
            if ($order->items()->count() === 0) {
                DB::rollBack();
                Log::error('Order processing failed: Order has no items', [
                    'order_id' => $order->id,
                ]);

                return response()->json([
                    'error' => 'Order has no items. Please try again.'
                ], 500);
            }

            // Record coupon redemption
            if ($order->coupon_id) {
                $this->couponService->recordRedemption(
                    $order->coupon,
                    auth()->user(),
                    $order->id
                );
            }

            // Clear cart
            $cart = $this->getCart($request);
            $this->cartService->clear($cart);

            DB::commit();

            app(OrderPlacementNotificationService::class)->dispatchAfterPlacement($order, OrderPlacementNotificationService::CHANNEL_ONLINE);

            return response()->json([
                'success' => true,
                'order_id' => $order->id,
                'redirect' => route('customer.orders.show', $order->id),
            ]);
        } catch (StockException|InsufficientStockException $e) {
            DB::rollBack();
            // Release reservations and cancel awaiting payment order on stock/payment finalization failure.
            $this->inventoryService->releaseByOrderId($order->id, true);
            PaymentLogger::error('Razorpay verify failed with stock error', [
                'order_id' => $order->id ?? null,
                'payment_id' => $data['razorpay_payment_id'] ?? null,
                'razorpay_order_id' => $data['razorpay_order_id'] ?? null,
                'error' => $e->getMessage(),
                'failure_bucket' => 'backend_state_race',
            ]);

            Log::error('Order processing failed: Stock error', [
                'order_id' => $order->id,
                'error' => $e->getMessage(),
                'context' => method_exists($e, 'getContext') ? $e->getContext() : [],
            ]);

            return response()->json([
                'error' => 'Failed to process order: ' . $e->getMessage()
            ], 409);
        } catch (\Exception $e) {
            DB::rollBack();
            // Release reservations and cancel awaiting payment order on payment flow failure.
            $this->inventoryService->releaseByOrderId($order->id, true);
            PaymentLogger::error('Razorpay verify failed with exception', [
                'order_id' => $order->id ?? null,
                'payment_id' => $data['razorpay_payment_id'] ?? null,
                'razorpay_order_id' => $data['razorpay_order_id'] ?? null,
                'error' => $e->getMessage(),
            ]);

            Log::error('Order processing failed', [
                'order_id' => $order->id,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json(['error' => 'Failed to process order: ' . $e->getMessage()], 500);
        }
    }

    public function createCodOrder(Request $request): JsonResponse
    {
        // Get valid shipping method codes for validation
        try {
            $validShippingCodes = ShippingMethod::active()->pluck('code')->toArray();
        } catch (\Exception $e) {
            $validShippingCodes = [];
        }

        $fulfillment = $this->validateCheckoutFulfillment($request, $validShippingCodes);
        if ($fulfillment instanceof JsonResponse) {
            return $fulfillment;
        }
        $fulfillmentType = $fulfillment['fulfillment_type'];

        $cart = $this->getCart($request);

        if ($cart->items->isEmpty()) {
            return response()->json(['error' => 'Cart is empty'], 400);
        }

        // Ensure cart totals are calculated (subtotal and discount)
        $this->cartService->recalculateTotals($cart);
        $cart->refresh();

        $subtotal = $cart->totals['subtotal'] ?? 0;
        $shippingPrice = $fulfillmentType === 'pickup'
            ? 0.0
            : $this->resolveShippingPrice($request->shipping_method, $subtotal);

        // Recalculate totals with the selected shipping method price
        $tax = $cart->totals['tax'] ?? 0;
        $discount = $cart->totals['discount'] ?? 0;
        // Since prices are tax-inclusive, subtotal already includes tax
        // Total = Subtotal - Discount + Shipping (tax already included in subtotal)
        $total = $subtotal - $discount + $shippingPrice;

        // Update cart totals with correct shipping
        $cart->update([
            'totals' => [
                'subtotal' => round($subtotal, 2),
                'tax' => round($tax, 2),
                'discount' => round($discount, 2),
                'shipping' => round($shippingPrice, 2),
                'total' => round($total, 2),
            ],
        ]);
        $cart->refresh();

        // Validate that totals exist and are properly calculated
        if (!$cart->totals || !isset($cart->totals['total']) || $cart->totals['total'] <= 0) {
            return response()->json(['error' => 'Cart totals could not be calculated'], 400);
        }

        try {
            $addressSnapshot = $this->buildCheckoutAddressSnapshot($request, $fulfillmentType);
        } catch (\InvalidArgumentException $e) {
            return response()->json(['error' => $e->getMessage()], 422);
        }

        try {
            DB::beginTransaction();

            // Create order record
            $order = Order::create([
                'user_id' => auth()->id(),
                'source' => 'online',
                'address_snapshot' => $addressSnapshot,
                'coupon_id' => $cart->coupon_id,
                'subtotal' => $cart->totals['subtotal'] ?? 0,
                'discount_total' => $cart->totals['discount'] ?? 0,
                'tax_total' => 0, // Will be calculated by ProfitService
                'shipping_total' => $cart->totals['shipping'] ?? 0,
                'total_payable' => $cart->totals['total'] ?? 0, // Will be recalculated after tax
                'payment_status' => Order::PAYMENT_PENDING, // PENDING is correct for COD - payment will be collected on delivery
                'payment_method' => 'cod',
                'status' => Order::STATUS_PLACED,
                'placed_at' => now(),
            ]);

            $lines = $cart->items->map(fn ($item) => [
                'product_id' => (int) $item->product_id,
                'qty' => (int) $item->qty,
            ])->values()->all();

            $fulfillmentType = $request->input('fulfillment_type', 'home_delivery');
            if ($request->filled('preferred_store_id')) {
                $order->update(['preferred_store_id' => $request->input('preferred_store_id')]);
            }

            try {
                if ($this->locationInventory->useLocationInventory()) {
                    $this->allocationService->allocateAndReserve(
                        $order->fresh(),
                        $lines,
                        $fulfillmentType === 'pickup' ? 'pickup' : 'home_delivery',
                        $addressSnapshot['pin'] ?? null,
                        null,
                        null,
                        true // allowSplit when Split Orders rule is active
                    );
                } else {
                    foreach ($lines as $line) {
                        $this->inventoryService->reserve($line['product_id'], $line['qty'], null, $order->id);
                    }
                }
            } catch (InsufficientStockException|StockException $e) {
                DB::rollBack();
                $this->locationInventory->useLocationInventory()
                    ? $this->locationInventory->releaseByOrderId($order->id)
                    : $this->inventoryService->releaseByOrderId($order->id);
                $order->delete();

                return response()->json(['error' => $e->getMessage()], 400);
            }

            // Create order items from cart with profit data.
            // Stock deduction happens only via confirmReservation (avoids double SALE movements).
            $cartItems = [];
            foreach ($cart->items as $cartItem) {
                $product = Product::where('id', $cartItem->product_id)->first();

                if (!$product) {
                    DB::rollBack();
                    $this->inventoryService->releaseByOrderId($order->id);
                    $order->delete();

                    return response()->json([
                        'error' => "Product not found: {$cartItem->product_id}"
                    ], 400);
                }

                $cartItems[] = [
                    'product_id' => $cartItem->product_id,
                    'qty' => $cartItem->qty,
                    'price' => $cartItem->price_at_add,
                ];
            }

            // Use ProfitService to create order items with profit calculations and tax calculation
            $this->profitService->allocateOrderCosts($order, $cartItems);
            
            // Refresh order to get calculated tax_total
            $order->refresh();
            
            // Recalculate total_payable
            // Prices are tax-inclusive, so total = subtotal - discount + shipping
            $order->update([
                'total_payable' => $order->subtotal - $order->discount_total + $order->shipping_total
            ]);

            // Record coupon redemption
            if ($order->coupon_id) {
                $this->couponService->recordRedemption(
                    $order->coupon,
                    auth()->user(),
                    $order->id
                );
            }

            // Confirm all stock reservations (single SALE path for COD)
            try {
                if ($this->locationInventory->useLocationInventory()) {
                    $locReservations = \App\Models\InventoryReservation::where('order_id', $order->id)
                        ->where('status', 'pending')
                        ->get();
                    foreach ($locReservations as $reservation) {
                        $this->locationInventory->confirmReservation($reservation->reservation_token);
                    }
                } else {
                    $reservations = \App\Models\StockReservation::where('order_id', $order->id)
                        ->where('status', 'pending')
                        ->get();
                    foreach ($reservations as $reservation) {
                        $this->inventoryService->confirmReservation($reservation->reservation_token);
                    }
                }
            } catch (StockException $e) {
                DB::rollBack();
                Log::error('Failed to confirm stock reservation for COD order', [
                    'order_id' => $order->id,
                    'error' => $e->getMessage(),
                ]);

                return response()->json([
                    'error' => 'Failed to process order: ' . $e->getMessage()
                ], 500);
            }

            // Clear cart
            $this->cartService->clear($cart);

            DB::commit();

            app(OrderPlacementNotificationService::class)->dispatchAfterPlacement($order, OrderPlacementNotificationService::CHANNEL_ONLINE);

            return response()->json([
                'success' => true,
                'order_id' => $order->id,
                'redirect' => route('customer.orders.index'),
            ]);
        } catch (InsufficientStockException $e) {
            DB::rollBack();
            // Release any reservations if order was created
            if (isset($order)) {
                $this->inventoryService->releaseByOrderId($order->id);
                $order->delete();
            }

            Log::warning('COD order creation failed: Insufficient stock', [
                'user_id' => auth()->id(),
                'error' => $e->getMessage(),
                'context' => $e->getContext(),
            ]);

            return response()->json([
                'error' => $e->getMessage()
            ], 400);
        } catch (StockException $e) {
            DB::rollBack();
            // Release any reservations if order was created
            if (isset($order)) {
                $this->inventoryService->releaseByOrderId($order->id);
                $order->delete();
            }

            Log::error('COD order creation failed: Stock error', [
                'user_id' => auth()->id(),
                'error' => $e->getMessage(),
                'context' => $e->getContext(),
            ]);

            return response()->json([
                'error' => $e->getMessage()
            ], 400);
        } catch (\Exception $e) {
            DB::rollBack();
            // Release any reservations if order was created
            if (isset($order)) {
                $this->inventoryService->releaseByOrderId($order->id);
                $order->delete();
            }

            Log::error('COD order creation failed', [
                'user_id' => auth()->id(),
                'cart_id' => $cart->id ?? null,
                'cart_totals' => $cart->totals ?? null,
                'cart_items_count' => $cart->items->count() ?? 0,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
            ]);

            return response()->json([
                'error' => 'Failed to create order: ' . $e->getMessage()
            ], 500);
        }
    }

    /**
     * @param  array<int, string>  $validShippingCodes
     * @return array{fulfillment_type: string}|JsonResponse
     */
    protected function validateCheckoutFulfillment(Request $request, array $validShippingCodes): array|JsonResponse
    {
        $request->validate([
            'fulfillment_type' => 'nullable|in:home_delivery,pickup',
            'address_id' => 'nullable|exists:addresses,id',
            'preferred_store_id' => 'nullable|exists:locations,id',
            'shipping_method' => $validShippingCodes ? 'nullable|in:' . implode(',', $validShippingCodes) : 'nullable|string',
        ]);

        $fulfillmentType = $request->input('fulfillment_type', 'home_delivery') === 'pickup'
            ? 'pickup'
            : 'home_delivery';

        if ($fulfillmentType === 'home_delivery' && !$request->filled('address_id')) {
            return response()->json(['error' => 'Please select a delivery address'], 422);
        }

        if ($fulfillmentType === 'pickup' && !$request->filled('preferred_store_id')) {
            return response()->json(['error' => 'Please select a pickup store'], 422);
        }

        return ['fulfillment_type' => $fulfillmentType];
    }

    /**
     * @return array<string, mixed>
     */
    protected function buildCheckoutAddressSnapshot(Request $request, string $fulfillmentType): array
    {
        $user = auth()->user();

        if ($fulfillmentType === 'pickup') {
            $store = Location::query()
                ->stores()
                ->allowsPickup()
                ->whereKey((int) $request->input('preferred_store_id'))
                ->first();

            if (!$store) {
                throw new \InvalidArgumentException('Selected pickup store is not available.');
            }

            $contactAddress = null;
            if ($request->filled('address_id')) {
                $contactAddress = Address::query()
                    ->where('id', $request->address_id)
                    ->where('user_id', $user->id)
                    ->first();
            }

            return [
                'name' => $contactAddress?->name ?: ($user->name ?: 'Customer'),
                'mobile' => $contactAddress?->mobile ?: ($user->mobile ?: ''),
                'line1' => $store->address_line1 ?: $store->name,
                'line2' => $store->address_line2,
                'city' => $store->city,
                'state' => $store->state,
                'country' => $store->country ?: 'India',
                'country_id' => null,
                'state_id' => null,
                'city_id' => null,
                'pin' => $store->pincode,
                'fulfillment' => 'pickup',
                'pickup_store_id' => $store->id,
                'pickup_store_name' => $store->name,
            ];
        }

        $address = Address::with(['country', 'stateRelation', 'cityRelation'])
            ->where('id', $request->address_id)
            ->where('user_id', $user->id)
            ->first();

        if (!$address) {
            throw new \InvalidArgumentException('Please select a delivery address');
        }

        return [
            'name' => $address->name,
            'mobile' => $address->mobile,
            'line1' => $address->line1,
            'line2' => $address->line2,
            'city' => $address->cityRelation?->name ?? $address->city,
            'state' => $address->stateRelation?->name ?? $address->state,
            'country' => $address->country?->name,
            'country_id' => $address->country_id,
            'state_id' => $address->state_id,
            'city_id' => $address->city_id,
            'pin' => $address->pin,
            'fulfillment' => 'home_delivery',
        ];
    }

    protected function resolveShippingPrice(?string $shippingMethodCode, float $subtotal): float
    {
        if (!$shippingMethodCode) {
            return 0;
        }

        try {
            $shippingMethod = ShippingMethod::where('code', $shippingMethodCode)
                ->where('is_active', true)
                ->first();

            if ($shippingMethod) {
                return $shippingMethod->calculateCharge($subtotal);
            }
        } catch (\Exception $e) {
            // Shipping methods table doesn't exist or error occurred, use default
        }

        return 0;
    }

    /**
     * Check if an existing pending order matches the current cart
     * Note: Failed orders are always reused without matching, this is only for pending orders
     */
    protected function orderMatchesCart(Order $order, $cart, $addressId, $shippingPrice): bool
    {
        // Check if order is recent (created within last 1 hour)
        $oneHourAgo = now()->subHour();
        if ($order->created_at < $oneHourAgo) {
            return false;
        }

        // Check if order matches current cart totals
        $cartSubtotal = $cart->totals['subtotal'] ?? 0;
        $cartTax = $cart->totals['tax'] ?? 0;
        $cartDiscount = $cart->totals['discount'] ?? 0;
        // Since prices are tax-inclusive, subtotal already includes tax
        $cartTotal = $cartSubtotal - $cartDiscount + $shippingPrice;

        if (abs($order->subtotal - $cartSubtotal) > 0.01 ||
            abs($order->tax_total - $cartTax) > 0.01 ||
            abs($order->discount_total - $cartDiscount) > 0.01 ||
            abs($order->shipping_total - $shippingPrice) > 0.01 ||
            abs($order->total_payable - $cartTotal) > 0.01) {
            return false;
        }

        // Check if coupon matches
        if ($order->coupon_id != $cart->coupon_id) {
            return false;
        }

        // Check if address matches (compare address snapshot)
        $address = Address::find($addressId);
        if ($address) {
            $orderAddress = $order->address_snapshot;
            if ($orderAddress['pin'] !== $address->pin ||
                $orderAddress['city'] !== $address->city ||
                $orderAddress['mobile'] !== $address->mobile) {
                return false;
            }
        }

        return true;
    }

    /**
     * Clean up old failed/pending orders with no items (older than 1 hour)
     */
    protected function cleanupOldFailedOrders(int $userId): void
    {
        $oneHourAgo = now()->subHour();

        // Clean up old failed/pending orders (older than 1 hour)
        // Note: Orders now have items, so we clean up based on age and status only
            $oldOrders = Order::where('user_id', $userId)
            ->where('status', Order::STATUS_AWAITING_PAYMENT)
            ->whereIn('payment_status', [Order::PAYMENT_PENDING, Order::PAYMENT_FAILED])
            ->where('created_at', '<', $oneHourAgo)
            ->get();

        foreach ($oldOrders as $order) {
            try {
                // Release stock reservations
                $this->inventoryService->releaseByOrderId($order->id, true);
            } catch (\Exception $e) {
                Log::warning('Failed to cleanup old order', [
                    'order_id' => $order->id,
                    'error' => $e->getMessage(),
                ]);
            }
        }
    }

}
