<?php

namespace App\Services;

use App\Models\Frame;
use App\Models\Lens;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class OrderService
{
    public function create(User $vendor, array $data): Order
    {
        return DB::transaction(function () use ($vendor, $data) {
            $preparedItems = [];
            $subtotal = 0;

            foreach ($data['items'] as $index => $item) {
                $product = $this->findProduct($vendor->id, $item['item_type'], (int) $item['item_id']);
                $quantity = (int) $item['quantity'];

                if (!$product->is_active) {
                    throw ValidationException::withMessages(["items.$index" => "{$product->name} is disabled."]);
                }

                if ($product->stock_quantity < $quantity) {
                    throw ValidationException::withMessages(["items.$index.quantity" => "Only {$product->stock_quantity} units of {$product->name} are available."]);
                }

                $unitPrice = array_key_exists('unit_price', $item) && $item['unit_price'] !== null
                    ? (float) $item['unit_price']
                    : (float) $product->price;
                $lineTotal = round($unitPrice * $quantity, 2);
                $subtotal += $lineTotal;

                $preparedItems[] = [
                    'product' => $product,
                    'item_type' => $item['item_type'],
                    'item_id' => $product->id,
                    'item_name' => $product->name,
                    'quantity' => $quantity,
                    'unit_price' => $unitPrice,
                    'total_price' => $lineTotal,
                ];
            }

            $discount = min((float) ($data['discount_amount'] ?? 0), $subtotal);
            $customer = !empty($data['customer_id'])
                ? User::where('role', 'customer')->find($data['customer_id'])
                : null;
            if (!empty($data['customer_id']) && !$customer) {
                throw ValidationException::withMessages([
                    'customer_id' => 'The selected account is not a customer.',
                ]);
            }

            $order = Order::create([
                'vendor_id' => $vendor->id,
                'customer_id' => $customer?->id,
                'order_number' => $this->uniqueNumber('ORD', 'order_number'),
                'invoice_number' => $this->uniqueNumber('INV', 'invoice_number'),
                'customer_name' => $data['customer_name'] ?? $customer?->name ?? 'Walk-in Customer',
                'customer_phone' => $data['customer_phone'] ?? $customer?->phone,
                'customer_email' => $data['customer_email'] ?? $customer?->email,
                'billing_address' => $data['billing_address'] ?? null,
                'status' => $data['status'] ?? 'pending',
                'payment_status' => $data['payment_status'] ?? 'unpaid',
                'subtotal' => $subtotal,
                'discount_amount' => $discount,
                'total' => max(0, $subtotal - $discount),
                'notes' => $data['notes'] ?? null,
                'stock_committed' => true,
                'ordered_at' => $data['ordered_at'] ?? now(),
            ]);

            foreach ($preparedItems as $item) {
                $item['product']->decrement('stock_quantity', $item['quantity']);
                unset($item['product']);
                $order->items()->create($item);
            }

            return $order->load(['items', 'customer']);
        });
    }

    public function updateStatus(Order $order, string $status, ?string $paymentStatus = null): Order
    {
        return DB::transaction(function () use ($order, $status, $paymentStatus) {
            $order->load('items');

            if ($status === 'cancelled' && $order->stock_committed) {
                $this->changeStock($order, 1);
                $order->stock_committed = false;
            } elseif ($status !== 'cancelled' && !$order->stock_committed) {
                $this->changeStock($order, -1);
                $order->stock_committed = true;
            }

            $order->status = $status;
            if ($paymentStatus !== null) {
                $order->payment_status = $paymentStatus;
            }
            $order->save();

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

    private function changeStock(Order $order, int $direction): void
    {
        foreach ($order->items as $index => $item) {
            $product = $this->findProduct($order->vendor_id, $item->item_type, $item->item_id);
            if ($direction < 0 && $product->stock_quantity < $item->quantity) {
                throw ValidationException::withMessages(["items.$index.quantity" => "Not enough stock to reactivate {$item->item_name}."]);
            }
            $direction > 0
                ? $product->increment('stock_quantity', $item->quantity)
                : $product->decrement('stock_quantity', $item->quantity);
        }
    }

    private function findProduct(int $vendorId, string $type, int $id): Frame|Lens
    {
        $model = $type === 'frame' ? Frame::class : Lens::class;

        return $model::where('vendor_id', $vendorId)->lockForUpdate()->findOrFail($id);
    }

    private function uniqueNumber(string $prefix, string $column): string
    {
        do {
            $number = $prefix . '-' . now()->format('ymd') . '-' . strtoupper(Str::random(6));
        } while (Order::where($column, $number)->exists());

        return $number;
    }
}
