<?php

namespace Webkul\Sale\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Auth;
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
use Webkul\Account\Models\MoveLine;
use Webkul\Account\Models\Tax;
use Webkul\Inventory\Enums as InventoryEnums;
use Webkul\Inventory\Models\Location;
use Webkul\Inventory\Models\Move as InventoryMove;
use Webkul\Inventory\Models\Route;
use Webkul\Inventory\Models\Rule;
use Webkul\Inventory\Models\Warehouse;
use Webkul\Partner\Models\Partner;
use Webkul\PluginManager\Package;
use Webkul\Product\Models\Packaging;
use Webkul\Product\Models\Product;
use Webkul\Sale\Database\Factories\OrderLineFactory;
use Webkul\Sale\Enums\OrderState;
use Webkul\Sale\Enums\QtyDeliveredMethod;
use Webkul\Sale\Facades\SaleOrder as SaleOrderFacade;
use Webkul\Security\Models\User;
use Webkul\Support\Models\Company;
use Webkul\Support\Models\Currency;
use Webkul\Support\Models\UOM;

class OrderLine extends Model implements Sortable
{
    use HasFactory, SortableTrait;

    protected $table = 'sales_order_lines';

    protected $fillable = [
        'sort',
        'order_id',
        'company_id',
        'currency_id',
        'order_partner_id',
        'salesman_id',
        'product_id',
        'product_uom_id',
        'linked_sale_order_sale_id',
        'creator_id',
        'state',
        'display_type',
        'virtual_id',
        'linked_virtual_id',
        'qty_delivered_method',
        'invoice_status',
        'analytic_distribution',
        'name',
        'product_uom_qty',
        'price_unit',
        'discount',
        'price_subtotal',
        'price_total',
        'price_reduce_taxexcl',
        'price_reduce_taxinc',
        'qty_delivered',
        'qty_invoiced',
        'qty_to_invoice',
        'untaxed_amount_invoiced',
        'untaxed_amount_to_invoice',
        'is_downpayment',
        'is_expense',
        'create_date',
        'write_date',
        'technical_price_unit',
        'price_tax',
        'product_qty',
        'product_packaging_qty',
        'product_packaging_id',
        'customer_lead',
        'purchase_price',
        'margin',
        'margin_percent',
        'warehouse_id',
    ];

    protected $casts = [
        'state'                => OrderState::class,
        'qty_delivered_method' => QtyDeliveredMethod::class,
    ];

    public $sortable = [
        'order_column_name'  => 'sort',
        'sort_when_creating' => true,
    ];

    public function order()
    {
        return $this->belongsTo(Order::class);
    }

    public function company()
    {
        return $this->belongsTo(Company::class);
    }

    public function currency()
    {
        return $this->belongsTo(Currency::class);
    }

    public function orderPartner()
    {
        return $this->belongsTo(Partner::class, 'order_partner_id');
    }

    public function salesman()
    {
        return $this->belongsTo(User::class, 'salesman_id');
    }

    public function product()
    {
        return $this->belongsTo(Product::class)->withTrashed();
    }

    public function uom()
    {
        return $this->belongsTo(UOM::class, 'product_uom_id');
    }

    public function taxes(): BelongsToMany
    {
        return $this->belongsToMany(Tax::class, 'sales_order_line_taxes', 'order_line_id', 'tax_id');
    }

    public function accountMoveLines()
    {
        return $this->belongsToMany(MoveLine::class, 'sales_order_line_invoices', 'order_line_id', 'invoice_line_id');
    }

    public function inventoryMoves(): HasMany
    {
        return $this->hasMany(InventoryMove::class, 'sale_order_line_id');
    }

    public function productPackaging()
    {
        return $this->belongsTo(Packaging::class);
    }

    public function linkedSaleOrderSale()
    {
        return $this->belongsTo(self::class, 'linked_sale_order_sale_id');
    }

    public function creator(): BelongsTo
    {
        return $this->belongsTo(User::class, 'creator_id');
    }

    public function warehouse(): BelongsTo
    {
        return $this->belongsTo(Warehouse::class, 'warehouse_id');
    }

    public function route(): BelongsTo
    {
        return $this->belongsTo(Route::class, 'route_id');
    }

    public function getExpectedDateAttribute()
    {
        if ($this->state == OrderState::SALE && $this->order->date_order) {
            $orderDate = $this->order->date_order;
        } else {
            $orderDate = now();
        }

        return $orderDate->addDays($this->customer_lead ?? 0);
    }

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($orderLine) {
            $orderLine->state ??= $orderLine->order->state;

            $orderLine->creator_id ??= Auth::id();
        });

        static::saving(function ($orderLine) {
            $orderLine->computeWarehouseId();
        });

        static::created(function ($orderLine) {
            if ($orderLine->order->state === OrderState::SALE) {
                SaleOrderFacade::applyInventoryRules(collect([$orderLine]));
            }
        });

        static::updated(function ($orderLine) {
            if (
                $orderLine->wasChanged('product_uom_qty')
                && $orderLine->state === OrderState::SALE
                && ! $orderLine->is_expense
            ) {
                $previousProductUomQty = [$orderLine->id => $orderLine->getOriginal('product_uom_qty')];

                SaleOrderFacade::applyInventoryRules(collect([$orderLine]), previousProductUOMQty: $previousProductUomQty);
            }
        });
    }

    public function computeWarehouseId()
    {
        if (! Package::isPluginInstalled('inventories')) {
            return;
        }

        $this->warehouse_id = $this->order->warehouse_id;

        if (! $this->route_id) {
            return;
        }

        $customerLocation = Location::where('type', InventoryEnums\LocationType::CUSTOMER)->first();

        $rules = Rule::where([
            ['destination_location_id', $customerLocation->id],
            ['action', '!=', 'push'],
            ['route_id', $this->route_id],
        ])
            ->orderBy('route_sort')
            ->orderBy('sort')
            ->get()
            ->sortBy(fn ($rule) => (! $rule->source_location_id || $rule->sourceLocation->warehouse_id === $this->order->warehouse_id) ? 0 : 1);

        if ($rules->isNotEmpty()) {
            $this->warehouse_id = $rules->first()->sourceLocation->warehouse_id;
        }
    }

    protected static function newFactory()
    {
        return OrderLineFactory::new();
    }
}
