<?php

namespace App\Services;

use App\Models\Order;
use App\Models\OrderItem;
use App\Models\ProductVariant;
use App\Models\StockCode;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use RuntimeException;

class CheckoutService
{
    public function __construct(
        private WalletService $walletService,
        private NotificationService $notifications,
        private CouponService $coupons,
        private OrderRewardService $rewards,
    ) {}

    public function createOrder(ProductVariant $product, array $payload, ?User $user = null): Order
    {
        return $this->createOrderFromCart(collect([[
            'product' => $product,
            'quantity' => 1,
        ]]), $payload, $user);
    }

    public function createOrderFromCart(Collection $cartItems, array $payload, ?User $user = null): Order
    {
        if ($cartItems->isEmpty()) {
            throw new RuntimeException('Cart is empty.');
        }

        return DB::transaction(function () use ($cartItems, $payload, $user) {
            $lockedItems = $cartItems->map(function (array $item) {
                $product = ProductVariant::whereKey($item['product']->id)->lockForUpdate()->firstOrFail();
                $quantity = max(1, (int) $item['quantity']);

                if ($product->status !== 'active') {
                    throw new RuntimeException($product->name_ar.' is not available.');
                }

                if ($product->stock_available < $quantity) {
                    throw new RuntimeException($product->name_ar.' stock is not enough.');
                }

                $unitPrice = $product->sale_price_minor ?? $product->price_minor;

                return [
                    'product' => $product,
                    'quantity' => $quantity,
                    'unit_price_minor' => $unitPrice,
                    'line_total_minor' => $unitPrice * $quantity,
                ];
            });

            $subtotal = $lockedItems->sum('line_total_minor');
            $currency = $lockedItems->first()['product']->currency_code;
            $quote = $this->coupons->quote($payload['coupon_code'] ?? null, $lockedItems, $user, $payload['customer_email'] ?? $user?->email);
            $total = $quote['total_minor'];

            if ($payload['payment_method'] === 'wallet') {
                if (! $user) {
                    throw new RuntimeException('Wallet payments require a signed-in account.');
                }

                $wallet = $this->walletService->ensureWallet($user, $currency);
                $this->walletService->debit($wallet, $total, 'order_payment', 'Cart order payment');
            }

            $order = Order::create([
                'uuid' => (string) Str::uuid(),
                'order_number' => $this->nextOrderNumber(),
                'user_id' => $user?->id,
                'customer_name' => $payload['customer_name'],
                'customer_email' => $payload['customer_email'] ?? $user?->email,
                'currency_code' => $currency,
                'exchange_rate_snapshot' => 1,
                'subtotal_minor' => $subtotal,
                'discount_minor' => $quote['discount_minor'],
                'total_minor' => $total,
                'coupon_id' => $quote['coupon']?->id,
                'coupon_code' => $quote['code'],
                'discount_snapshot' => $quote['snapshot'],
                'payment_method' => $payload['payment_method'],
                'payment_status' => $payload['payment_method'] === 'wallet' ? 'paid' : 'pending',
                'fulfillment_status' => 'pending',
                'customer_fields' => [
                    'player_id' => $payload['player_id'] ?? null,
                    'server' => $payload['server'] ?? null,
                    'notes' => $payload['notes'] ?? null,
                ],
                'paid_at' => $payload['payment_method'] === 'wallet' ? now() : null,
            ]);

            foreach ($lockedItems as $line) {
                $product = $line['product'];
                $quantity = $line['quantity'];

                $item = OrderItem::create([
                    'order_id' => $order->id,
                    'product_variant_id' => $product->id,
                    'name_snapshot' => $product->name_ar,
                    'sku_snapshot' => $product->sku,
                    'quantity' => $quantity,
                    'unit_price_minor' => $line['unit_price_minor'],
                    'total_minor' => $line['line_total_minor'],
                    'delivery_method_snapshot' => $product->delivery_method,
                    'fulfillment_status' => 'pending',
                ]);

                if ($product->delivery_method === 'stored_code') {
                    $codes = StockCode::where('product_variant_id', $product->id)
                        ->where('status', 'available')
                        ->lockForUpdate()
                        ->limit($quantity)
                        ->get();

                    if ($codes->count() < $quantity) {
                        throw new RuntimeException('No stored codes are currently available for '.$product->name_ar.'.');
                    }

                    foreach ($codes as $code) {
                        $code->update([
                            'status' => 'reserved',
                            'reserved_order_id' => $order->id,
                            'sold_order_item_id' => $item->id,
                            'reserved_at' => now(),
                        ]);
                    }
                }

                $product->decrement('stock_available', $quantity);
            }

            if ($user) {
                $user->increment('orders_count');
                $user->increment('total_spent_minor', $total);
            }

            if ($quote['coupon']) {
                $this->coupons->redeem($quote['coupon'], $order, $quote['discount_minor'], $user);
            }

            $this->notifications->orderCreated($order);

            if ($order->payment_status === 'paid') {
                $this->rewards->apply($order, $user);
                $this->notifications->paymentConfirmed($order);
            }

            return $order->load('items');
        });
    }

    private function nextOrderNumber(): string
    {
        return 'RN-'.now()->format('ymd').'-'.str_pad((string) (Order::count() + 1001), 4, '0', STR_PAD_LEFT);
    }
}
