<?php

namespace Modules\Order\Models;

use Carbon\Carbon;
use DB;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Collection;
use Modules\Branch\Traits\HasBranch;
use Modules\Cart\CartDiscount;
use Modules\Cart\CartItem;
use Modules\Cart\CartTax;
use Modules\Loyalty\Services\LoyaltyGift\LoyaltyGiftServiceInterface;
use Modules\Order\Enums\OrderDiscountType;
use Modules\Order\Enums\OrderPaymentStatus;
use Modules\Order\Enums\OrderProductStatus;
use Modules\Order\Enums\OrderStatus;
use Modules\Order\Enums\OrderType;
use Modules\Payment\Enums\PaymentMethod;
use Modules\Payment\Models\Payment;
use Modules\Pos\Models\PosRegister;
use Modules\Pos\Models\PosSession;
use Modules\Pos\Services\PosCashMovement\PosCashMovementServiceInterface;
use Modules\SeatingPlan\Models\Table;
use Modules\SeatingPlan\Models\TableMerge;
use Modules\Support\Eloquent\Model;
use Modules\Support\Money;
use Modules\Support\Traits\HasCreatedBy;
use Modules\Support\Traits\HasFilters;
use Modules\Support\Traits\HasSortBy;
use Modules\Tax\Enums\TaxType;
use Modules\User\Models\User;
use Modules\Voucher\Models\Voucher;
use Throwable;

/**
 * @property int $id
 * @property int|null $table_id
 * @property-read Table|null $table
 * @property int|null $waiter_id
 * @property-read User|null $waiter
 * @property int|null $cashier_id
 * @property-read User|null $cashier
 * @property int|null $pos_register_id
 * @property-read PosRegister|null $posRegister
 * @property int|null $table_merge_id
 * @property-read TableMerge|null $tableMerge
 * @property int|null $merged_into_order_id
 * @property-read Order|null $mergedIntoOrder
 * @property int|null $customer_id
 * @property-read User|null $customer
 * @property int|null $merged_by
 * @property-read PosRegister|null $mergedBy
 * @property-read Collection<OrderTax> $taxes
 * @property-read Collection<OrderProduct> $products
 * @property int|null $pos_session_id
 * @property-read PosSession|null $posSession
 * @property string $reference_no
 * @property string $order_number
 * @property-read  OrderStatus|null $previous_status
 * @property OrderStatus $status
 * @property-read  OrderStatus|null $next_status
 * @property OrderType $type
 * @property OrderPaymentStatus $payment_status
 * @property-read OrderDiscount $discount
 * @property string $currency
 * @property float $currency_rate
 * @property Money $subtotal
 * @property Money $total
 * @property Money $due_amount
 * @property Money $cost_price
 * @property Money $revenue
 * @property int $guest_count
 * @property string|null $notes
 * @property string|null $car_plate
 * @property string|null $car_description
 * @property Carbon $order_date
 * @property boolean $is_stock_deducted
 * @property int|null $modified_by
 * @property User|null $modifiedBy
 * @property Carbon|null $modified_at
 * @property Carbon|null $served_at
 * @property Carbon|null $closed_at
 * @property Carbon|null $merged_at
 * @property Carbon|null $payment_at
 * @property Carbon|null $scheduled_at
 * @property Carbon|null $created_at
 * @property Carbon|null $updated_at
 *
 * @method static Builder activeOrders()
 * @method static Builder forMerge()
 * @method static Builder completed()
 * @method static Builder withoutCanceledOrders()
 *
 */
class Order extends Model
{
    use HasCreatedBy,
        HasFilters,
        HasSortBy,
        HasBranch;

    /**
     * Default date column
     *
     * @var string
     */
    public static string $defaultDateColumn = 'created_at';

    /**
     * The attributes that aren't mass assignable.
     *
     * @var array
     */
    protected $guarded = [];

    /**
     * Get total sales (converted to default currency if needed)
     *
     * @param string|null $currency
     * @return Money
     */
    public static function totalSales(?string $currency = null): Money
    {
        if (auth()->check() && auth()->user()->assignedToBranch()) {
            $total = self::withoutCanceledOrders()->sum('total');
        } else {
            $total = self::withoutCanceledOrders()
                ->selectRaw('SUM(total * COALESCE(currency_rate, 1)) AS total_sales')
                ->value('total_sales') ?? 0;
        }

        return is_null($currency) ? Money::inDefaultCurrency($total) : new Money($total, $currency);
    }

    /**
     * Get average order value (converted to default currency if needed)
     *
     * @param string|null $currency
     * @return Money
     */
    public static function averageOrderValue(?string $currency = null): Money
    {
        if (auth()->check() && auth()->user()->assignedToBranch()) {
            $avg = self::withoutCanceledOrders()->average('total') ?? 0;
        } else {
            $avg = self::withoutCanceledOrders()
                ->selectRaw('AVG(total * COALESCE(currency_rate, 1)) AS avg_order')
                ->value('avg_order') ?? 0;
        }

        return is_null($currency) ? Money::inDefaultCurrency($avg) : new Money($avg, $currency);
    }

    /**
     * Boot the model and handle stock adjustments on create, update, and delete.
     *
     * @return void
     */
    protected static function booted(): void
    {
        static::creating(function (Order $order) {
            $order->reference_no = static::generateReferenceNo();
            $order->order_number = static::generateOrderNumber($order->branch_id);
        });
    }

    /**
     * Generate reference no
     *
     * @return string
     */
    public static function generateReferenceNo(): string
    {
        return mb_strtoupper("ORD-" . substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, 10));
    }

    /**
     * Generate order number
     *
     * @param int $branchId
     * @return string
     */
    public static function generateOrderNumber(int $branchId): string
    {
        $count = Order::where('branch_id', $branchId)
            ->whereDate('order_date', today())
            ->count();

        return str_pad($count + 1, $count < 100 ? 2 : 3, '0', STR_PAD_LEFT);
    }

    /**
     * Get a model modified by
     *
     * @return BelongsTo
     */
    public function modifiedBy(): BelongsTo
    {
        return $this->belongsTo(User::class, "modified_by")
            ->withOutGlobalBranchPermission()
            ->withoutGlobalActive()
            ->withTrashed();
    }

    /**
     * Get a waiter
     *
     * @return BelongsTo
     */
    public function waiter(): BelongsTo
    {
        return $this->belongsTo(User::class, "waiter_id")
            ->withOutGlobalBranchPermission()
            ->withoutGlobalActive()
            ->withTrashed();
    }

    /**
     * Get cashier
     *
     * @return BelongsTo
     */
    public function cashier(): BelongsTo
    {
        return $this->belongsTo(User::class, "cashier_id")
            ->withOutGlobalBranchPermission()
            ->withoutGlobalActive()
            ->withTrashed();
    }

    /**
     * Get customer
     *
     * @return BelongsTo
     */
    public function customer(): BelongsTo
    {
        return $this->belongsTo(User::class, "customer_id")
            ->withoutGlobalActive()
            ->withTrashed();
    }

    /** @inheritDoc */
    public function allowedFilterKeys(): array
    {
        return [
            "search",
            "status",
            "payment_status",
            "type",
            "from",
            "to",
            "group_by_date",
            "customer_id",
            self::BRANCH_COLUMN_NAME
        ];
    }

    /**
     * Scope a query to search across all fields.
     *
     * @param Builder $query
     * @param string $value
     * @return void
     */
    public function scopeSearch(Builder $query, string $value): void
    {
        $query->where(function (Builder $query) use ($value) {
            $query->like('reference_no', $value)->orLike('order_number', $value);
        });
    }

    /**
     * Scope a query to get all active orders.
     *
     * @param Builder $query
     * @return Builder
     */
    public function scopeActiveOrders(Builder $query): Builder
    {
        return $query->where(function ($query) {
            $query
                ->where(function (Builder $query) {
                    $query->whereNot('payment_status', OrderPaymentStatus::Paid)
                        ->orWhereIn('status', [
                            OrderStatus::Pending,
                            OrderStatus::Confirmed,
                            OrderStatus::Preparing,
                            OrderStatus::Ready,
                            OrderStatus::Served,
                        ]);
                })
                ->whereNotIn('status', [
                    OrderStatus::Cancelled,
                    OrderStatus::Refunded,
                    OrderStatus::Merged,
                ]);
        });
    }

    /**
     * Scope a query to get all orders by type.
     *
     * @param Builder $query
     * @param string $type
     * @return void
     */
    public function scopeType(Builder $query, string $type): void
    {
        $query->where("orders.type", $type);
    }

    /**
     * Scope a query to get all orders completed.
     *
     * @param Builder $query
     * @return void
     */
    public function scopeCompleted(Builder $query): void
    {
        $query->where("status", OrderStatus::Completed);
    }

    /**
     * Scope a query to get all without canceled orders.
     *
     * @param Builder $query
     * @return void
     */
    public function scopeWithoutCanceledOrders(Builder $query): void
    {
        $query->whereNotIn("status", [OrderStatus::Cancelled, OrderStatus::Refunded, OrderStatus::Merged]);
    }

    /**
     * Scope a query to get all active orders.
     *
     * @param Builder $query
     * @return void
     */
    public function scopeForMerge(Builder $query): void
    {
        $query->where(function ($query) {
            $query
                ->where('type', OrderType::DineIn)
                ->whereNot('payment_status', OrderPaymentStatus::Paid)
                ->whereIn('status', [
                    OrderStatus::Pending,
                    OrderStatus::Confirmed,
                    OrderStatus::Preparing,
                    OrderStatus::Ready,
                    OrderStatus::Served,
                ]);
        });
    }

    /**
     * Get due amount
     *
     * @return Attribute
     */
    public function dueAmount(): Attribute
    {
        return Attribute::get(fn($amount) => isset($this->currency) ? new Money($amount, $this->currency) : Money::inDefaultCurrency($amount));
    }

    /**
     * Get cost price
     *
     * @return Attribute
     */
    public function costPrice(): Attribute
    {
        return Attribute::get(fn($amount) => isset($this->currency) ? new Money($amount, $this->currency) : Money::inDefaultCurrency($amount));
    }

    /**
     * Get revenue
     *
     * @return Attribute
     */
    public function revenue(): Attribute
    {
        return Attribute::get(fn($amount) => isset($this->currency) ? new Money($amount, $this->currency) : Money::inDefaultCurrency($amount));
    }

    /**
     * Get total taxes
     *
     * @return Money
     */
    public function totalTax(): Money
    {
        $total = 0;

        if ($this->hasTax()) {
            $this->taxes()
                ->get()
                ->each(function ($tax) use (&$total) {
                    $total += $tax->amount->amount();
                });
        }

        return isset($this->currency)
            ? new Money($total, $this->currency)
            : Money::inDefaultCurrency($total);
    }

    /**
     * Determine if order has tax or not
     *
     * @return bool
     */
    public function hasTax(): bool
    {
        return $this->taxes->isNotEmpty();
    }

    /**
     * Get order taxes
     *
     * @return HasMany
     */
    public function taxes(): HasMany
    {
        return $this->hasMany(OrderTax::class)
            ->whereNull("order_product_id");
    }

    /**
     * Determine if order has a discount or not
     *
     * @return bool
     */
    public function hasDiscount(): bool
    {
        return !is_null($this->discount);
    }

    /**
     * Store product
     *
     * @param CartItem $cartItem
     * @return void
     * @throws Throwable
     */
    public function storeOrUpdateProduct(CartItem $cartItem): void
    {
        $giftService = app(LoyaltyGiftServiceInterface::class);

        $params = [
            'product_id' => $cartItem->product->id,
            'currency' => $this->currency,
            'loyalty_gift_id' => $cartItem->loyaltyGift?->id(),
            'currency_rate' => $this->currency_rate,
            'quantity' => $cartItem->qty ?: 1,
            'unit_price' => $cartItem->unitPrice()->amount(),
            'subtotal' => $cartItem->subtotal()->amount(),
            'tax_total' => $cartItem->taxTotal()->amount(),
            'total' => $cartItem->total()->amount(),
        ];

        if (in_array($cartItem->orderProduct?->status(), [OrderProductStatus::Cancelled, OrderProductStatus::Refunded])) {
            $params['status'] = $cartItem->orderProduct->status();
        }

        if (!is_null($cartItem->orderProduct?->id())) {
            /** @var OrderProduct $orderProduct */
            $orderProduct = $this->products()
                ->where('id', $cartItem->orderProduct->id())
                ->first();
            $orderProduct->update($params);
        } else {
            /** @var OrderProduct $orderProduct */
            $orderProduct = $this->products()->create($params);
            $orderProduct->storeOptions($cartItem->options);
            if (!is_null($orderProduct->loyalty_gift_id)) {
                $giftService->useGift(loyaltyGiftId: $orderProduct->loyalty_gift_id, order: $this);
            }
        }

        $orderProduct->storeOrUpdateTaxes($cartItem->taxes());
    }

    /**
     * Get subtotal
     *
     * @return Attribute
     */
    public function subtotal(): Attribute
    {
        return Attribute::get(fn($amount) => isset($this->currency) ? new Money($amount, $this->currency) : Money::inDefaultCurrency($amount));
    }

    /**
     * Get total
     *
     * @return Attribute
     */
    public function total(): Attribute
    {
        return Attribute::get(fn($amount) => isset($this->currency) ? new Money($amount, $this->currency) : Money::inDefaultCurrency($amount));
    }

    /**
     * Get products
     *
     * @return HasMany
     */
    public function products(): HasMany
    {
        return $this->hasMany(OrderProduct::class);
    }

    /**
     * Store Or Update Taxes
     *
     * @param Collection $taxes
     * @return void
     */
    public function storeOrUpdateTaxes(Collection $taxes): void
    {
        foreach ($taxes as $tax) {
            $this->storeOrUpdateTax($tax);
        }
    }

    /**
     * Store Or Update Tax
     *
     * @param CartTax $tax
     * @return void
     */
    public function storeOrUpdateTax(CartTax $tax): void
    {
        $this->taxes()
            ->updateOrCreate(
                ['tax_id' => $tax->id()],
                [
                    'name' => $tax->translationsName(),
                    'rate' => $tax->rate(),
                    'currency' => $tax->currency(),
                    'currency_rate' => $this->currency_rate,
                    'amount' => $tax->amount()->amount(),
                    'type' => $tax->type(),
                    'compound' => $tax->compound(),
                ]
            );
    }

    /**
     * Get pos register
     *
     * @return BelongsTo
     */
    public function posRegister(): BelongsTo
    {
        return $this->belongsTo(PosRegister::class)
            ->withOutGlobalBranchPermission()
            ->withoutGlobalActive()
            ->withTrashed();
    }

    /**
     * Store payments
     *
     * @param array $payments
     * @return void
     * @throws Throwable
     */
    public function storePayments(array $payments): void
    {
        foreach ($payments as $payment) {
            $this->storePayment($payment);
        }
    }

    /**
     * Store payment
     *
     * @param array $data
     * @return void
     * @throws Throwable
     */
    public function storePayment(array $data): void
    {
        /** @var Payment $payment */
        $payment = $this->payments()->create([
            "order_reference_no" => $this->reference_no,
            'branch_id' => $this->branch_id,
            'cashier_id' => $data['cashier_id'] ?? null,
            'transaction_id' => $data['transaction_id'] ?? null,
            'method' => $data['method'],
            'amount' => $data['amount'],
            'currency' => $this->currency,
            'currency_rate' => $this->currency_rate,
            'meta' => $data['meta'] ?? null,
        ]);

        if ($data['method'] == PaymentMethod::Cash->value) {
            app(PosCashMovementServiceInterface::class)
                ->sale(
                    session: $data['session'],
                    amount: abs($data['amount']),
                    orderId: $this->id,
                    paymentId: $payment->id,
                );
        }
    }

    /**
     * Get order payments
     *
     * @return HasMany
     */
    public function payments(): HasMany
    {
        return $this->hasMany(Payment::class)
            ->withOutGlobalBranchPermission()
            ->withTrashed();
    }

    /**
     * Get pos session
     *
     * @return BelongsTo
     */
    public function posSession(): BelongsTo
    {
        return $this->belongsTo(PosSession::class)
            ->withOutGlobalBranchPermission()
            ->withTrashed();
    }

    /**
     * Determine if order allow cancel
     *
     * @return bool
     */
    public function cancelIsAllowed(): bool
    {
        return in_array(
            $this->status,
            [
                OrderStatus::Pending,
                OrderStatus::Confirmed,
                OrderStatus::Preparing,
                OrderStatus::Ready,
            ]
        );
    }

    /**
     * Determine if order allow refund
     *
     * @return bool
     */
    public function refundIsAllowed(): bool
    {
        if ($this->type == OrderType::DineIn) {
            $allowedRefund = $this->status == OrderStatus::Served;
        } else {
            $allowedRefund = $this->status == OrderStatus::Completed;
        }

        return $this->created_at->toDateString() == today()->toDateString() && $allowedRefund;
    }

    /**
     * Store status log
     *
     * @param OrderStatus $status
     * @param int|null $changedById
     * @param int|null $reasonId
     * @param string|null $note
     * @return OrderStatusLog
     */
    public function storeStatusLog(
        OrderStatus $status,
        ?int        $changedById = null,
        ?int        $reasonId = null,
        ?string     $note = null,
    ): OrderStatusLog
    {
        return $this->statusLogs()
            ->create([
                "changed_by" => $changedById,
                "reason_id" => $reasonId,
                "status" => $status,
                "note" => $note
            ]);
    }

    /**
     * Get order status logs
     *
     * @return HasMany
     */
    public function statusLogs(): HasMany
    {
        return $this->hasMany(OrderStatusLog::class)->orderBy("id", "desc");
    }

    /**
     * Determine if order allow to add payment
     *
     * @return bool
     */
    public function allowAddPayment(): bool
    {
        return !$this->payment_status->isPaid()
            && in_array(
                $this->status,
                [
                    OrderStatus::Ready,
                    OrderStatus::Served,
                    OrderStatus::Preparing,
                    OrderStatus::Confirmed
                ]
            );
    }

    /**
     * Get next status
     *
     * @return Attribute
     */
    public function nextStatus(): Attribute
    {
        return Attribute::get(
            fn() => match ($this->status) {
                OrderStatus::Pending => OrderStatus::Confirmed,
                OrderStatus::Confirmed => OrderStatus::Preparing,
                OrderStatus::Preparing => OrderStatus::Ready,
                OrderStatus::Ready => $this->type === OrderType::DineIn ? OrderStatus::Served : OrderStatus::Completed,
                OrderStatus::Served => OrderStatus::Completed,
                default => null
            }
        );
    }

    /**
     * Get previous status
     *
     * @return Attribute
     */
    public function previousStatus(): Attribute
    {
        return Attribute::get(
            fn() => match ($this->status) {
                OrderStatus::Confirmed => OrderStatus::Pending,
                OrderStatus::Preparing => OrderStatus::Confirmed,
                OrderStatus::Ready => OrderStatus::Preparing,
                OrderStatus::Served => OrderStatus::Ready,
                OrderStatus::Completed => $this->type === OrderType::DineIn ? OrderStatus::Served : OrderStatus::Ready,
                default => null
            }
        );
    }

    /**
     * Get table merge
     *
     * @return BelongsTo
     */
    public function tableMerge(): BelongsTo
    {
        return $this->belongsTo(TableMerge::class)
            ->withOutGlobalBranchPermission()
            ->withTrashed();
    }

    /**
     * Get merged into order
     *
     * @return BelongsTo
     */
    public function mergedIntoOrder(): BelongsTo
    {
        return $this->belongsTo(Order::class)
            ->withOutGlobalBranchPermission();
    }

    /**
     * Get merged by
     *
     * @return BelongsTo
     */
    public function mergedBy(): BelongsTo
    {
        return $this->belongsTo(User::class, "merged_by")
            ->withOutGlobalBranchPermission()
            ->withoutGlobalActive()
            ->withTrashed();
    }

    /**
     * Recalculate Order
     *
     * @param bool $deleteTaxesDuplicates
     * @return void
     */
    public function recalculate(bool $deleteTaxesDuplicates = false): void
    {
        if ($deleteTaxesDuplicates) {
            $this->deleteTaxesDuplicates();
        }

        $this->load(["taxes", "products"]);

        $subtotal = $this->products->sum(fn(OrderProduct $orderProduct) => $orderProduct->total->amount());
        $costPrice = $this->products->sum(fn(OrderProduct $orderProduct) => $orderProduct->cost_price->amount());
        $revenue = $this->products->sum(fn(OrderProduct $orderProduct) => $orderProduct->revenue->amount());
        $totalTaxes = $this->recalculateTaxes($subtotal);

        $this->update([
            "subtotal" => $subtotal,
            "total" => $subtotal + $totalTaxes,
            "cost_price" => $costPrice,
            "revenue" => $revenue,
        ]);

        $this->refreshDueAmount();
    }

    /**
     * Delete duplicate taxes by tax_id across given orders.
     *
     * Keeps the first record (lowest id) for each tax_id
     * and deletes the rest.
     *
     * @return int number of deleted rows
     */
    public function deleteTaxesDuplicates(): int
    {
        return DB::table('order_taxes as t1')
            ->join(
                'order_taxes as t2',
                fn($join) => $join->on('t1.tax_id', '=', 't2.tax_id')
                    ->on('t1.id', '>', 't2.id')
                    ->on('t1.order_id', '=', 't2.order_id')
            )
            ->where('t1.order_id', $this->id)
            ->whereNull('t1.order_product_id')
            ->delete();
    }

    /**
     * Get order table
     *
     * @return BelongsTo
     */
    public function table(): BelongsTo
    {
        return $this->belongsTo(Table::class)
            ->withOutGlobalBranchPermission()
            ->withoutGlobalActive()
            ->withTrashed();
    }

    /**
     * Recalculate order taxes
     *
     * @param float $basePrice
     * @return float
     */
    public function recalculateTaxes(float $basePrice): float
    {
        $totalTaxes = 0;

        foreach ($this->taxes as $tax) {
            $taxAmount = 0;

            if ($tax->type === TaxType::Exclusive) {
                $taxAmount = $basePrice * ($tax->rate / 100);
            }

            $totalTaxes += $taxAmount;

            if ($tax->compound) {
                $basePrice += $taxAmount;
            }

            $tax->update(["amount" => $taxAmount]);
        }

        return $totalTaxes;
    }

    /**
     * Refresh due amount
     *
     * @return void
     */
    public function refreshDueAmount(): void
    {
        $paid = round($this->payments()->sum('amount'), 3);
        $due = round(max($this->total->amount() - $paid, 0), 3);

        $paymentStatus = $paid == 0
            ? OrderPaymentStatus::Unpaid
            : ($paid < $this->total->round(3)->amount() ? OrderPaymentStatus::PartiallyPaid : OrderPaymentStatus::Paid);

        $this->updateQuietly([
            'due_amount' => $due,
            'payment_status' => $paymentStatus,
            "payment_at" => $paymentStatus == OrderPaymentStatus::Paid ? now() : null,
        ]);
    }

    /**
     * Print receipt allowed
     *
     * @return bool
     */
    public function printReceiptAllowed(): bool
    {
        return $this->payment_status->isPaid() && is_null($this->table_merge_id);
    }

    /**
     * Get refunded amount
     *
     * @return Money
     */
    public function getRefundedAmount(): Money
    {
        return $this->total->subtract($this->due_amount);
    }

    /**
     * Check if the order is scheduled for today (ignores time of day).
     *
     * @return bool
     */
    public function isScheduledForToday(): bool
    {
        if ($this->scheduled_at) {
            return $this->scheduled_at->toDateString() <= now()->toDateString();
        }

        return true;
    }

    /**
     * Determine allow edit order or not
     *
     * @return bool
     */
    public function editIsAllowed(): bool
    {
        return in_array(
            $this->status,
            [
                OrderStatus::Pending,
                OrderStatus::Confirmed,
                OrderStatus::Preparing,
                OrderStatus::Ready,
                OrderStatus::Served,
            ]
        );
    }

    /**
     * Store or update discount
     *
     * @param CartDiscount|null $discount
     * @return void
     * @throws Throwable
     */
    public function storeOrUpdateDiscount(?CartDiscount $discount): void
    {

        /** @var OrderDiscount|null $orderDiscount */
        $orderDiscount = $this->discount;
        $oldLoyaltyGiftId = $orderDiscount?->loyalty_gift_id;

        $amount = $discount?->value();

        if (is_null($amount) || $amount->amount() == 0) {
            $discount = null;
        }

        $giftService = app(LoyaltyGiftServiceInterface::class);

        if (!is_null($oldLoyaltyGiftId) && $oldLoyaltyGiftId != $discount?->loyaltyGift()?->id()) {
            $giftService->rollbackGift(loyaltyGiftId: $oldLoyaltyGiftId, order: $this);
        }

        if ((is_null($discount) || $amount->isZero()) && $orderDiscount) {
            $orderDiscount->discountable?->unusedOnce();
            $orderDiscount->delete();
            return;
        }

        if (is_null($discount)) {
            return;
        }

        $discountModel = $discount->model();

        $data = [
            'discountable_type' => $discountModel->getMorphClass(),
            'discountable_id' => $discountModel->id,
            'type' => $discountModel instanceof Voucher
                ? OrderDiscountType::Discount
                : OrderDiscountType::Voucher,
            'currency' => $this->currency,
            'currency_rate' => $this->currency_rate,
            'loyalty_gift_id' => $discount->loyaltyGift()?->id(),
            'amount' => $amount->amount(),
        ];

        if ($orderDiscount) {
            $oldType = $orderDiscount->discountable_type;
            $oldId = $orderDiscount->discountable_id;

            $orderDiscount->update($data);

            if ($oldType !== $data['discountable_type'] || $oldId !== $data['discountable_id']) {
                $orderDiscount->discountable?->unusedOnce();
                $discountModel->usedOnce();
            }

            return;
        }


        /** @var OrderDiscount $orderDiscount */
        $orderDiscount = $this->discount()->create($data);
        $discountModel->usedOnce();

        if (!is_null($orderDiscount->loyalty_gift_id) && $oldLoyaltyGiftId != $orderDiscount->loyalty_gift_id) {
            $giftService->useGift(loyaltyGiftId: $orderDiscount->loyalty_gift_id, order: $this);
        }
    }

    /**
     * Get discount
     *
     * @return HasOne<OrderDiscount,static>
     */
    public function discount(): HasOne
    {
        return $this->hasOne(OrderDiscount::class);
    }

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            "status" => OrderStatus::class,
            "type" => OrderType::class,
            "payment_status" => OrderPaymentStatus::class,
            "order_date" => "date",
            "served_at" => "datetime",
            "closed_at" => "datetime",
            "merged_at" => "datetime",
            "payment_at" => "datetime",
            "modified_at" => "datetime",
            "is_stock_deducted" => "boolean",
            "start_date" => "datetime",
            "end_date" => "datetime",
            "scheduled_at" => "datetime",
        ];
    }

    /** @inheritDoc */
    protected function getSortableAttributes(): array
    {
        return [
            "reference_no",
            "order_number",
            "status",
            "type",
            "total",
            "payment_status",
            "customer_id",
            self::BRANCH_COLUMN_NAME,
        ];
    }
}
