<?php

namespace App\Modules\Stock;

use Illuminate\Support\Facades\Config;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\ValidationModel;
use App\Modules\Stock\StockGroup;
use App\Modules\Bookings\Order;
use App\Modules\Stock\StockReport;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use App\Models\Status;
use Illuminate\Support\Arr;

class Stock extends ValidationModel
{
    use SoftDeletes;
    use \Venturecraft\Revisionable\RevisionableTrait;
    use HasFactory;

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'stock';

    /**
     * The attributes that should be mutated to dates.
     *
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = ['vat', 'retail_price', 'cost', 'last_report'];

    /**
     * Array of validation rules.
     *
     * @var  array
     */
    protected static $rules = [
        'name'              => 'required',
        // 'group_id'          => 'required_if:type,stock',
        'type'                => 'required',
        'model_number'        => 'nullable|unique:stock,model_number,$id,id,deleted_at,NULL',
        'name'                => 'unique:stock,name,$id,id,deleted_at,NULL',
    ];

    /**
     * The model's attributes.
     *
     * @var array
     */
    protected $attributes = [
        'approval_required'    => 0,
        'quantity_to_order'    => 0,
        'quantity'             => 0,
        'buffer'               => 0,
        'severity_id'          => 2,
    ];

    /**
     * The relations to eager load on every query.
     *
     * @var array
     */
    protected $with = ['group', 'composite_items'];

    /**
     *	The attributes that won't be assigned during
     *	a mass assignment.
     */
    protected $fillable = ['name', 'model_number', 'buffer', 'group_id', 'type', 'quantity_to_order', 'discontinued', 'supplier_id', 'status_id', 'quantity', 'price_excluding_vat', 'model_number', 'ordered_quantity', 'unlimited', 'kashflow_category_id', 'kashflow_purchase_category_id', 'notes', 'approval_required', 'severity_id', 'requested_date', 'composite'];

    // ==================================================================
    //
    // Discontinued setter and getter
    //
    // ------------------------------------------------------------------


    /**
     *	Inverse one to many relationship.
     *	Return the group for this stock item.
     */
    public function group()
    {
        return $this->belongsTo(StockGroup::class);
    }

    /**
     * Scope results to service items only.
     */
    public function scopeService($query)
    {
        return $query->where('type', 'service');
    }

    /**
     * Filter results to stock type only.
     */
    public function scopeStockOnly($query)
    {
        return $query->where('type', 'stock');
    }

    /**
     * Retrieves the vat amount for the product.
     *
     * @return float
     */
    public function getVatAttribute()
    {
        return number_format($this->price_excluding_vat * Config::get('settings.vat') / 100, 2, '.', '');
    }

    /**
     * Retrieves the cost amount for the product.
     *
     * @return float
     */
    public function getCostAttribute()
    {
        if ($this->composite) {
            $cost = 0;
            foreach ($this->composite_items as $item) {
                if ($latest_order = StockPurchaseOrderItem::query()->where('stock_id', $item->id)->orderBy('created_at', 'DESC')->first()) {
                    $cost += $latest_order->unit_cost * $item->pivot->quantity;
                } else {
                    return 'NA';
                }
            }
        } else {
            $latest_order = StockPurchaseOrderItem::query()->where('stock_id', $this->id)->orderBy('created_at', 'DESC')->first();

            if (!$latest_order) {
                return 'NA';
            }

            $cost = $latest_order->unit_cost;
        }

        return $cost;
    }

    /**
     * Sets the quantity to order = 0 if it is less than 0.
     *
     * @return float
     */
    public function setQuantityToOrderAttribute($quantity)
    {
        $this->attributes['quantity_to_order'] = $quantity < 0 ? 0 : $quantity;
    }

    /**
     * Sets the quantity = 0 if it is an unlimited product.
     */
    public function setQuantityAttribute($quantity)
    {
        if ($this->unlimited) {
            $this->attributes['quantity'] = 0;
        } elseif ($quantity < 0) {
            throw new \Exception("There isn't a sufficient quantity in stock for this product.");
        } else {
            $this->attributes['quantity'] = $quantity;
        }
    }

    /**
     * Sets the quantity = 0 if it is an unlimited product.
     */
    public function setOrderedQuantityAttribute($ordered_quantity)
    {
        if ($ordered_quantity < 0) {
            throw new \Exception('Ordered quantity can not be negative. Please, talk to the stock administrator.');
        } else {
            $this->attributes['ordered_quantity'] = $ordered_quantity;
        }
    }

    /**
     * Calculates the retail price excluding vat.
     *
     * @return float
     */
    public function getRetailPriceAttribute()
    {
        return number_format($this->price_excluding_vat + $this->vat, 2, '.', '');
    }

    /**
     * Calculates the retail price excluding vat.
     *
     * @return App\Modules\Stock\StockReport
     */
    public function getLastReportAttribute()
    {
        return $this->reports()->first();
    }

    /**
     * Helper function that retrieves the divisor, knowing the VAT value.
     *
     * @return float
     */
    public static function getDivisor()
    {
        return Config::get('settings.vat') / 100 + 1;
    }

    /**
     * Returns the order associated with this part.
     *
     * @return App\Modules\Bookings\Order
     */
    public function order()
    {
        return $this->belongsTo(Order::class, 'order_id');
    }

    /**
     * Relationship: query builder to return all the composite items for this stock item.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function composite_items()
    {
        return $this->belongsToMany(Stock::class, 'stock_composite_items', 'parent_id', 'child_id')->withTimestamps()->withPivot('quantity');
    }

    /**
     * Relationship: query builder to return all reports for this stock item.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function reports()
    {
        return $this->hasMany(StockReport::class)->orderBy('created_at', 'DESC');
    }

    /**
     * Return the total amount required for this stock.
     *
     * @return int
     */
    public function quantity_required()
    {
        return \App\Modules\Bookings\Part::query()->where('stock_id', $this->id)->where('quantity_on_order', '>', 0)->leftJoin('tasks', 'parts.related_id', '=', 'tasks.id')->where('related_type', 'task')->where('tasks.status_id', '!=', Status::ORDERS_CANCELLED)->where('tasks.status_id', '!=', Status::TASKS_CANCELLED)->sum('quantity_on_order');
    }

    /**
     * Return the total amount availbale for this stock.
     *
     * @return int
     */
    public function quantity_available()
    {
        $quantity_available = $this->quantity - $this->quantity_required();

        return $quantity_available < 0 ? 0 : $quantity_available;
    }

    /**
     * Return a copy of the stock given a parameter with a different name.
     * If the second paramter is false, the copy won't include items if the stock is a composite item.
     *
     * @param 	Stock 	$stock
     * @param 	bool $with_items
     *
     * @return Stock
     */
    public static function duplicate(Stock $stock, $with_items)
    {
        $newStock = self::create([
            'name'                 => $stock->name . ' (' . rand(1, 100) . ')',
            'composite'            => $stock->composite,
            'price_excluding_vat'  => $stock->price_excluding_vat,
            'buffer'               => $stock->buffer,
            'group_id'             => $stock->group_id,
            'type'                 => $stock->type,
            'severity_id'          => $stock->severity_id,
            'type' => $stock->type,
            'purchase_type' => $stock->purchase_type,
        ]);

        if (!$newStock->exists) {
            return self::duplicate($stock, $with_items);
        }

        if ($with_items && $stock->composite) {
            $newStock->composite_items()->sync(Arr::pluck($stock->composite_items(), 'id'));
        }

        return $newStock;
    }

    /**
     * Check if the part composite items are the same than in the original template.
     *
     * @return bool
     */
    public static function sameItems(Stock $stock, $items)
    {
        $original_items = $stock->composite_items()->get();

        if (count($original_items) != count($items)) {
            return false;
        }

        foreach ($original_items as $item) {
            // Check if the item exists in the new items array
            if (!array_key_exists($item->id, $items)) {
                return false;
            }

            // Safely get the quantity, defaulting to 0 if not set
            $new_quantity = 0;
            if (is_array($items[$item->id]) && array_key_exists('quantity', $items[$item->id])) {
                $new_quantity = $items[$item->id]['quantity'];
            } elseif (is_numeric($items[$item->id])) {
                $new_quantity = $items[$item->id];
            }
            
            if ($item->pivot->quantity != $new_quantity) {
                return false;
            }
        }

        return true;
    }
}
