<?php

namespace App\Services;

use App\Exceptions\CartException;
use App\Models\Cart;
use App\Models\InventoryMovement;
use App\Models\Order;
use App\Models\ProductVariant;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

/**
 * Turns a cart into an order: snapshots pricing and product details onto
 * order_items (Step 2's design — history must survive later product
 * edits), deducts stock immediately at placement (not at some later
 * "confirmed" status) to avoid overselling while an order sits pending,
 * and creates the initial payment record for the admin's approve/reject
 * workflow (Step 11).
 */
class OrderService
{
    public function __construct(private readonly CartService $cartService) {}

    /**
     * @param  array<string, mixed>  $data
     */
    public function placeOrder(Cart $cart, array $data): Order
    {
        if ($cart->items->isEmpty()) {
            throw new CartException('Your cart is empty.');
        }

        // Stock can have moved since the item was added to the cart (a
        // concurrent order, an admin adjustment) — re-check right before
        // committing rather than trusting what the cart page last showed.
        foreach ($cart->items as $item) {
            $available = $item->variant->inventory?->available_quantity ?? 0;

            if ($item->quantity > $available) {
                throw new CartException(
                    $available > 0
                        ? "Only {$available} of \"{$item->product->name}\" left in stock — please update your cart."
                        : "\"{$item->product->name}\" just sold out — please remove it from your cart."
                );
            }
        }

        return DB::transaction(function () use ($cart, $data) {
            $totals = $this->cartService->totals($cart);

            $order = Order::create([
                'order_number' => $this->generateOrderNumber(),
                'user_id' => auth()->id(),
                'customer_name' => $data['customer_name'],
                'customer_phone' => $data['customer_phone'],
                'customer_email' => $data['customer_email'] ?? null,
                'shipping_address' => $data['shipping_address'],
                'shipping_city' => $data['shipping_city'],
                'shipping_area' => $data['shipping_area'],
                'order_notes' => $data['order_notes'] ?? null,
                'subtotal' => $totals['subtotal'],
                'shipping_charge' => $totals['shipping'],
                'discount_amount' => $totals['discount'],
                'coupon_id' => $cart->coupon_id,
                'coupon_code' => $cart->coupon?->code,
                'grand_total' => $totals['grand_total'],
                'payment_method' => $data['payment_method'],
                'payment_status' => 'pending',
                'order_status' => 'pending',
            ]);

            foreach ($cart->items as $item) {
                $order->items()->create([
                    'product_id' => $item->product_id,
                    'product_variant_id' => $item->product_variant_id,
                    'product_name' => $item->product->name,
                    'sku' => $item->variant->sku,
                    'color' => $item->variant->color?->name,
                    'size' => $item->variant->size?->name,
                    'price' => $item->price,
                    'quantity' => $item->quantity,
                    'subtotal' => $item->line_total,
                ]);

                $this->deductStock($item->variant, $item->quantity, $order);
            }

            $order->payments()->create([
                'method' => $data['payment_method'],
                'amount' => $totals['grand_total'],
                'status' => 'pending',
                'reference_number' => $data['payment_reference'] ?? null,
                'proof_image' => $data['payment_proof'] ?? null,
            ]);

            $order->statusHistories()->create(['status' => 'pending', 'note' => 'Order placed.']);

            if ($cart->coupon_id) {
                $cart->coupon()->increment('used_count');
            }

            $cart->items()->delete();
            $cart->update(['coupon_id' => null]);

            return $order->fresh(['items', 'payments']);
        });
    }

    private function deductStock(ProductVariant $variant, int $quantity, Order $order): void
    {
        $variant->inventory->decrement('quantity', $quantity);

        InventoryMovement::create([
            'product_variant_id' => $variant->id,
            'order_id' => $order->id,
            'type' => 'out',
            'quantity' => $quantity,
            'reason' => 'Order placed',
        ]);
    }

    /**
     * Admin-driven status change (Step 12). Cancelling restores the stock
     * that was deducted at placement — it was only ever "reserved" by a
     * pending order, and a cancelled order shouldn't keep it locked up.
     * Only fires once: re-cancelling an already-cancelled order, or
     * changing a cancelled order to something else, does not move stock
     * again — that's a deliberate scope boundary, not an oversight; a
     * reactivated order needing stock back is an admin inventory
     * adjustment (Step 13), not an automatic side effect here.
     */
    public function updateStatus(Order $order, string $newStatus, ?string $note = null): Order
    {
        return DB::transaction(function () use ($order, $newStatus, $note) {
            $wasAlreadyCancelled = $order->order_status->value === 'cancelled';

            if ($newStatus === 'cancelled' && ! $wasAlreadyCancelled) {
                $this->restoreStock($order);
            }

            $order->update([
                'order_status' => $newStatus,
                'cancelled_reason' => $newStatus === 'cancelled' ? $note : $order->cancelled_reason,
            ]);

            $order->statusHistories()->create([
                'status' => $newStatus,
                'note' => $note,
                'changed_by' => auth()->id(),
            ]);

            return $order->fresh(['items', 'statusHistories']);
        });
    }

    private function restoreStock(Order $order): void
    {
        foreach ($order->items as $item) {
            $variant = $item->variant;

            if (! $variant || ! $variant->inventory) {
                continue;
            }

            $variant->inventory->increment('quantity', $item->quantity);

            InventoryMovement::create([
                'product_variant_id' => $variant->id,
                'order_id' => $order->id,
                'type' => 'in',
                'quantity' => $item->quantity,
                'reason' => 'Order cancelled — stock restored',
            ]);
        }
    }

    private function generateOrderNumber(): string
    {
        do {
            $number = 'NYR-'.now()->format('Ymd').'-'.Str::upper(Str::random(4));
        } while (Order::where('order_number', $number)->exists());

        return $number;
    }
}
