<?php

namespace Modules\Order\Services\UpdateOrder;

use DB;
use Illuminate\Support\Collection;
use Modules\Cart\CartItem;
use Modules\Cart\Facades\EditOrderCart;
use Modules\Currency\Models\CurrencyRate;
use Modules\Order\Enums\OrderProductAction;
use Modules\Order\Enums\OrderProductStatus;
use Modules\Order\Enums\OrderStatus;
use Modules\Order\Enums\OrderType;
use Modules\Order\Events\OrderUpdateStatus;
use Modules\Order\Models\Order;
use Modules\Order\Models\OrderProduct;
use Modules\Order\Services\Order\OrderServiceInterface;
use Modules\Pos\Services\PosCashMovement\PosCashMovementServiceInterface;

class UpdateOrderService implements UpdateOrderServiceInterface
{
    /** @inheritDoc */
    public function update(string|int $id, array $data): Order
    {
        return DB::transaction(function () use ($id, $data) {
            $orderService = app(OrderServiceInterface::class);

            $order = $orderService->findOrFail($id, true);
            $order->load(['products']);

            $data['currency_rate'] = CurrencyRate::for($order->currency);

            $data['total'] = EditOrderCart::total()->round(3)->amount();
            $amountPaid = $order->total->amount() - $order->due_amount->amount();
            $overpaidAmount = 0;

            if ($amountPaid > $data['total']) {
                $overpaidAmount = round($amountPaid - $data['total'], 3);
                $posSession = app(PosCashMovementServiceInterface::class)
                    ->getPosActiveSession(
                        $data['pos_register_id'],
                        __("order::orders.overpaid_amount")
                    );
            }

            abort_if(
                $overpaidAmount > 0 && !isset($data['refund_payment_method']),
                400,
                __("validation.required", ["attribute" => __("order::attributes.orders.refund_payment_method")])
            );

            abort_if($data['total'] <= 0, 400, __("order::messages.order_must_contain_at_least_one_active_product"));

            $order = $this->updateOrder($order, $data);


            $this->storeOrUpdateOrderProducts(
                $order,
                $this->parseProductActions(EditOrderCart::items(), $order->products)
            );
            $order->storeOrUpdateTaxes(EditOrderCart::taxes());
            $order->storeOrUpdateDiscount(EditOrderCart::discount());

            if ($overpaidAmount > 0) {
                $order->storePayment([
                    'cashier_id' => auth()->user()->id,
                    'method' => $data['refund_payment_method'],
                    'amount' => -$overpaidAmount,
                    'meta' => ['reason' => 'Order changed and overpaid'],
                    "session" => $posSession ?? null,
                ]);
            } else {
                $order->refreshDueAmount();
            }

            if (
                !in_array($order->status, [OrderStatus::Pending, OrderStatus::Confirmed])
                && $order->products()->where('status', OrderProductStatus::Pending)->exists()
            ) {
                $order->update(["status" => OrderStatus::Preparing]);
                event(
                    new OrderUpdateStatus(
                        order: $order,
                        status: OrderStatus::Preparing,
                        note: "Order was modified. Status changed back to Preparing.",
                    )
                );
            }
            EditOrderCart::clear();
            return $order;
        });
    }

    /**
     * Update order
     *
     * @param Order $order
     * @param array $data
     * @return Order
     */
    private function updateOrder(Order $order, array $data): Order
    {
        $order->update([
            "customer_id" => EditOrderCart::customer()?->id() ?: $order->customer_id,
            "modified_at" => now(),
            "modified_by" => auth()->id(),
            "currency_rate" => $data['currency_rate'],
            "subtotal" => EditOrderCart::subtotal()->amount(),
            "total" => $data['total'],
            "pos_register_id" => $data['pos_register_id'],
            "due_amount" => $data['total'] - $order->due_amount->amount(),
            ...($order->type == OrderType::DriveThru
                ? [
                    "car_plate" => $data['car_plate'],
                    "car_description" => $data['car_description'],
                ]
                : []),
            "scheduled_at" => in_array($order->type, [OrderType::PreOrder, OrderType::Catering])
                ? ($data['scheduled_at'] ?? null)
                : null,
            "guest_count" => $data['guest_count'] ?? 1,
            "notes" => $data["notes"] ?? null,
        ]);

        return $order->fresh();
    }

    /**
     * Update or store order products
     *
     * @param Order $order
     * @param Collection $products
     * @return void
     */
    private function storeOrUpdateOrderProducts(Order $order, Collection $products): void
    {
        foreach ($products as $product) {
            $order->storeOrUpdateProduct($product);
        }
    }

    /**
     * Parse Product actions
     *
     * @param Collection $items
     * @param Collection $orderProducts
     * @return Collection
     */
    private function parseProductActions(Collection $items, Collection $orderProducts): Collection
    {
        $data = collect();
        $orderProducts = $orderProducts->keyBy('id');
        $itemsMap = $items
            ->filter(fn($item) => !is_null($item->orderProduct))
            ->keyBy(fn($item) => $item->orderProduct->id());

        $deletedProductIds = [];
        /** @var OrderProduct $orderProduct */
        foreach ($orderProducts as $orderProduct) {
            if ($orderProduct->status === OrderProductStatus::Pending && !isset($itemsMap[$orderProduct->id])) {
                $deletedProductIds[] = $orderProduct->id;
            }
        }

        OrderProduct::query()->whereIn('id', $deletedProductIds)->delete();

        /** @var CartItem $item */
        foreach ($items as $item) {

            if (!is_null($item->orderProduct) && isset($orderProducts[$item->orderProduct->id()])) {
                $exceptedQuantity = collect(array_values($item->actions))->sum('quantity');
                /** @var OrderProduct $originalProduct */
                $originalProduct = $orderProducts[$item->orderProduct->id()];
                if ($item->qty == 0) {
                    $item->qty = $exceptedQuantity;
                }
                $quantity = $item->qty;
                if (in_array($originalProduct->status, [OrderProductStatus::Cancelled, OrderProductStatus::Refunded])) {
                    $item->orderProduct->setStatus($originalProduct->status);
                }

                if (!empty($item->actions) && ($quantity - $exceptedQuantity) >= 0) {
                    foreach ($item->actions as $action) {
                        abort_if(
                            ($action['id'] == OrderProductAction::Cancel->value && $originalProduct->status != OrderProductStatus::Preparing)
                            || $action['id'] == OrderProductAction::Refund->value && !in_array($originalProduct->status, [OrderProductStatus::Served, OrderProductStatus::Ready]),
                            400,
                            __("core::errors.an_unexpected_error_occurred")
                        );
                    }

                    if ($quantity === $exceptedQuantity) {
                        $item->orderProduct->setStatus(
                            OrderProductAction::from(array_values($item->actions)[0]['id'])->getProductStatus()
                        );
                        $item->qty = $exceptedQuantity;
                        $data->push($item);
                    } else {
                        $data->push($item);
                        $item->orderProduct->setId();
                        foreach ($item->actions as $action) {
                            $newItem = clone $item;
                            $newItem->qty = $action['quantity'];
                            $newItem->orderProduct->setStatus(
                                OrderProductAction::from($action['id'])->getProductStatus()
                            );
                            $data->push($newItem);
                        }
                    }
                } else {
                    $data->push($item);
                }
            } else {
                $data->push($item);
            }
        }

        return $data;
    }
}
