<?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\City;
use App\Models\StockMovement;
use App\Models\VariantStockMovement;
use App\Services\CartService;
use App\Services\CouponService;
use App\Services\InventoryService;
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;
use App\Models\WhatsappTemplates;
use App\Utility\WhatsappUtility;
use PDF;

class CheckoutController extends Controller
{
    public function __construct(
        protected CartService $cartService,
        protected RazorpayService $razorpayService,
        protected CouponService $couponService,
        protected InventoryService $inventoryService,
        protected ProfitService $profitService
    ) {
        $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 = [];
        foreach ($cart->items as $item) {
            // Unified model: product is the actual sellable item
            $product = $item->product;
            $availableStock = $product->current_stock;
            $itemName = $product->name;
            
            // 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);
        }

        $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';

        return view('checkout.index', compact('cart', 'addresses', 'shippingMethods', 'razorpayEnabled', 'codEnabled'));
    }

    public function storeAddress(StoreAddressRequest $request): RedirectResponse
    {
        $data = $request->validated();
        $data['user_id'] = auth()->id();

        $state = State::find($data['state_id']);
        $city = City::find($data['city_id']);

        // Store readable names for backward compatibility and snapshots
        $data['state'] = $state?->name;
        $data['city'] = $city?->name;

        // If this is set as default, unset other defaults
        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 createRazorpayOrder(Request $request): JsonResponse
    {
        // Get valid shipping method codes for validation
        try {
            $validShippingCodes = ShippingMethod::active()->pluck('code')->toArray();
        } catch (\Exception $e) {
            $validShippingCodes = [];
        }
        
        $request->validate([
            'address_id' => 'required|exists:addresses,id',
            'shipping_method' => $validShippingCodes ? 'nullable|in:' . implode(',', $validShippingCodes) : 'nullable|string',
        ]);

        $cart = $this->getCart($request);

        if ($cart->items->isEmpty()) {
            return response()->json(['error' => 'Cart is empty'], 400);
        }

        // Get selected shipping method and its price
        $shippingPrice = 0;
        if ($request->shipping_method) {
            try {
                $shippingMethod = ShippingMethod::where('code', $request->shipping_method)
                    ->where('is_active', true)
                    ->first();
                
                if ($shippingMethod) {
                    $shippingPrice = (float) $shippingMethod->price;
                }
            } catch (\Exception $e) {
                // Shipping methods table doesn't exist or error occurred, use default
                $shippingPrice = 0;
            }
        }

        // Ensure cart totals are calculated (subtotal and discount)
        $this->cartService->recalculateTotals($cart);
        $cart->refresh();

        // Recalculate totals with the selected shipping method price
        $subtotal = $cart->totals['subtotal'] ?? 0;
        $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);
        }

        $address = Address::with(['country', 'stateRelation', 'cityRelation'])
            ->where('id', $request->address_id)
            ->where('user_id', auth()->id())
            ->firstOrFail();

        $addressSnapshot = [
            '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,
        ];

        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,
                ]);
                }
            }

            // Reserve stock for all cart items (with database locks to prevent race conditions)
            // Note: For variant products, we skip reservations and handle stock directly in checkout
            $reservationTokens = [];
            foreach ($cart->items as $item) {
                $product = Product::find($item->product_id);
                
                // Unified model: All products use the same reservation system
                // product_id is the actual sellable product
                try {
                    $token = $this->inventoryService->reserve(
                        $item->product_id,
                        $item->qty,
                        null, // Use default TTL (5 minutes)
                        $order->id
                    );
                    $reservationTokens[$item->product_id] = $token;
                } catch (InsufficientStockException $e) {
                    DB::rollBack();
                    // Release any already created reservations
                    $this->inventoryService->releaseByOrderId($order->id);
                    // Delete the order
                    $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', [
                        'ga_purchase' => 1,
                        'order_id' => $order->id,
                    ]),
                    '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', [
                            'ga_purchase' => 1,
                            'order_id' => $order->id,
                        ]),
                        '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.');
            }

            foreach ($reservations as $reservation) {
                $this->inventoryService->confirmReservation($reservation->reservation_token);
            }

            // Capture payment only after stock is safely confirmed.
            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)
            // Now we just need to create stock movements and confirm stock when payment succeeds
            // Check if order has items (idempotency check)
            if ($order->items()->count() === 0) {
                DB::rollBack();
                Log::error('Order processing failed: Order has no items', [
                    'order_id' => $order->id,
                ]);

                // Send WhatsApp invoice PDF
                $this->sendWhatsAppInvoicePdf($order);

                return response()->json([
                    'error' => 'Order has no items. Please try again.'
                ], 500);
            }

            // Create stock movements for sale (only when payment succeeds)
            foreach ($order->items as $orderItem) {
                // Lock product for update to prevent race conditions
                $product = Product::where('id', $orderItem->product_id)
                    ->lockForUpdate()
                    ->first();

                if (!$product) {
                    DB::rollBack();
                    Log::error('Order processing failed: Product not found', [
                        'order_id' => $order->id,
                        'product_id' => $orderItem->product_id,
                    ]);

                    return response()->json([
                        'error' => "Product not found: {$orderItem->product_id}"
                    ], 400);
                }

                // Check if stock movement already exists for this order (idempotency check)
                // This prevents double deduction when retrying payment or if reservation confirmation already created movements
                // Single products use StockMovement, variant products use VariantStockMovement (matches Product::recalculateStock)
                $existingMovements = null;
                $totalDeducted = 0;
                
                if ($product->isVariant()) {
                    // For variant products, check VariantStockMovement
                    $existingMovements = VariantStockMovement::where('product_id', $product->id)
                        ->where('type', VariantStockMovement::TYPE_SALE)
                        ->where('notes', "Order #{$order->id}")
                        ->get();
                } else {
                    // For single products, check StockMovement
                    $existingMovements = StockMovement::where('product_id', $product->id)
                        ->where('type', StockMovement::TYPE_SALE)
                        ->where('notes', "Order #{$order->id}")
                        ->get();
                }
                
                $totalDeducted = abs($existingMovements->sum('quantity')); // Sum of absolute values (quantities are negative)

                // If stock movements already exist for this order, skip creating new ones
                if ($totalDeducted >= $orderItem->qty) {
                    Log::info('Skipped stock movement creation: Already deducted for this order', [
                        'order_id' => $order->id,
                        'product_id' => $product->id,
                        'product_type' => $product->isVariant() ? 'variant' : 'single',
                        'order_item_qty' => $orderItem->qty,
                        'already_deducted' => $totalDeducted,
                    ]);
                    continue; // Skip to next item
                }

                // Unified model: Check product stock directly
                $currentStock = $product->current_stock;
                
                // Calculate remaining quantity to deduct (if partial deduction already exists)
                $remainingQty = $orderItem->qty - $totalDeducted;
                
                if ($currentStock < $remainingQty) {
                    DB::rollBack();
                    Log::error('Order processing failed: Insufficient stock', [
                        'order_id' => $order->id,
                        'product_id' => $product->id,
                        'available' => $currentStock,
                        'requested' => $remainingQty,
                        'already_deducted' => $totalDeducted,
                    ]);

                    return response()->json([
                        'error' => "Insufficient stock for product: {$product->name}. Available: {$currentStock}, Requested: {$remainingQty}"
                    ], 400);
                }

                // Create stock movement: single products use StockMovement, variant products use VariantStockMovement
                $movementPayload = [
                    'product_id' => $product->id,
                    'quantity' => -$remainingQty, // Negative for sale, only deduct remaining quantity
                    'notes' => "Order #{$order->id}",
                    'created_by' => auth()->id(),
                ];
                
                if ($product->isVariant()) {
                    VariantStockMovement::create(array_merge($movementPayload, ['type' => VariantStockMovement::TYPE_SALE]));
                } else {
                    StockMovement::create(array_merge($movementPayload, ['type' => StockMovement::TYPE_SALE]));
                }

                // Recalculate stock
                $product->recalculateStock();
            }

            // 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();

            // Send WhatsApp invoice PDF
            $this->sendWhatsAppInvoicePdf($order);

            return response()->json([
                'success' => true,
                'order_id' => $order->id,
                'redirect' => route('customer.orders.index', [
                    'ga_purchase' => 1,
                    'order_id' => $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 = [];
        }
        
        $request->validate([
            'address_id' => 'required|exists:addresses,id',
            'shipping_method' => $validShippingCodes ? 'nullable|in:' . implode(',', $validShippingCodes) : 'nullable|string',
        ]);

        $cart = $this->getCart($request);

        if ($cart->items->isEmpty()) {
            return response()->json(['error' => 'Cart is empty'], 400);
        }

        // Get selected shipping method and its price
        $shippingPrice = 0;
        if ($request->shipping_method) {
            try {
                $shippingMethod = ShippingMethod::where('code', $request->shipping_method)
                    ->where('is_active', true)
                    ->first();
                
                if ($shippingMethod) {
                    $shippingPrice = (float) $shippingMethod->price;
                }
            } catch (\Exception $e) {
                // Shipping methods table doesn't exist or error occurred, use default
                $shippingPrice = 0;
            }
        }

        // Ensure cart totals are calculated (subtotal and discount)
        $this->cartService->recalculateTotals($cart);
        $cart->refresh();

        // Recalculate totals with the selected shipping method price
        $subtotal = $cart->totals['subtotal'] ?? 0;
        $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);
        }

        $address = Address::with(['country', 'stateRelation', 'cityRelation'])
            ->where('id', $request->address_id)
            ->where('user_id', auth()->id())
            ->firstOrFail();

        $addressSnapshot = [
            '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,
        ];

        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(),
            ]);

            // Reserve stock for all cart items
            // Note: For variant products, we skip reservations and handle stock directly in checkout
            foreach ($cart->items as $item) {
                $product = Product::find($item->product_id);
                
                // Unified model: All products use the same reservation system
                // product_id is the actual sellable product
                try {
                    $this->inventoryService->reserve(
                        $item->product_id,
                        $item->qty,
                        null, // Use default TTL (5 minutes)
                        $order->id
                    );
                } catch (InsufficientStockException $e) {
                    DB::rollBack();
                    // Release any already created reservations
                    $this->inventoryService->releaseByOrderId($order->id);
                    // Delete the order
                    $order->delete();
                    
                    return response()->json([
                        'error' => $e->getMessage()
                    ], 400);
                }
            }

            // Create order items from cart with profit data and create stock movements
            $cartItems = [];
            foreach ($cart->items as $cartItem) {
                // Lock product for update to prevent race conditions
                $product = Product::where('id', $cartItem->product_id)
                    ->lockForUpdate()
                    ->first();

                if (!$product) {
                    DB::rollBack();
                    $this->inventoryService->releaseByOrderId($order->id);
                    $order->delete();

                    return response()->json([
                        'error' => "Product not found: {$cartItem->product_id}"
                    ], 400);
                }

                // Unified model: Check product stock directly
                $currentStock = $product->current_stock;
                
                if ($currentStock < $cartItem->qty) {
                    DB::rollBack();
                    $this->inventoryService->releaseByOrderId($order->id);
                    $order->delete();

                    return response()->json([
                        'error' => "Insufficient stock for product: {$product->name}. Available: {$currentStock}, Requested: {$cartItem->qty}"
                    ], 400);
                }

                // Create stock movement for sale (use VariantStockMovement for all products)
                VariantStockMovement::create([
                    'product_id' => $product->id,
                    'type' => \App\Models\VariantStockMovement::TYPE_SALE,
                    'quantity' => -$cartItem->qty, // Negative for sale
                    'notes' => "Order #{$order->id}",
                    'created_by' => auth()->id(),
                ]);

                // Recalculate stock
                $product->recalculateStock();

                // 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
            ]);

            // Record coupon redemption
            if ($order->coupon_id) {
                $this->couponService->recordRedemption(
                    $order->coupon,
                    auth()->user(),
                    $order->id
                );
            }

            // Confirm all stock reservations (this will decrement stock)
            // Note: Variant products don't use reservations, stock is handled directly above
            $reservations = \App\Models\StockReservation::where('order_id', $order->id)
                ->where('status', 'pending')
                ->get();

            foreach ($reservations as $reservation) {
                try {
                    $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,
                        'reservation_token' => $reservation->reservation_token,
                        'error' => $e->getMessage(),
                    ]);

                    return response()->json([
                        'error' => 'Failed to process order: ' . $e->getMessage()
                    ], 500);
                }
            }

            // Clear cart
            $this->cartService->clear($cart);

            DB::commit();

            // Send WhatsApp invoice PDF
            $this->sendWhatsAppInvoicePdf($order);

            return response()->json([
                'success' => true,
                'order_id' => $order->id,
                'redirect' => route('customer.orders.index', [
                    'ga_purchase' => 1,
                    'order_id' => $order->id,
                ]),
            ]);
        } 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);
        }
    }

    /**
     * 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(),
                ]);
            }
        }
    }

    protected function sendWhatsAppInvoicePdf(Order $order): void
    {
        // Send Invoice PDF via WhatsApp
        $WhatsAppTemplate = WhatsappTemplates::where('identifier', 'order_placement')->first();
        
        if ($WhatsAppTemplate && $WhatsAppTemplate->is_active == 1) {
            try {
                $customer = auth()->user(); 
                
                if ($customer) {
                    $pdf = PDF::loadView('admin.orders.thermal_invoice', [
                        'order' => $order
                    ]);

                    $pdfDir = public_path('storage/invoices');
                    if (!is_dir($pdfDir)) {
                        mkdir($pdfDir, 0755, true);
                    }

                    $pdfPath = $pdfDir . DIRECTORY_SEPARATOR . 'order-' . $order->id . '.pdf';
                    $pdf->save($pdfPath);

                    $pdfUrl = asset('storage/invoices/order-' . $order->id . '.pdf');
                    WhatsappUtility::order_placement($customer, $order, $pdfUrl);
                }
            } catch (\Exception $e) {
                \Log::error('WhatsApp PDF sending failed: ' . $e->getMessage());
            }
        }
    }

}
