<?php
namespace App\Models\StockManagement\Traits;

use App\Exceptions\StockManagement\CheckStockProductWhenOrderingException;
use App\Models\OrderManagement\OrderProduct;
use App\Models\StockManagement\AdjustmentOperation;
use App\Models\StockManagement\AdjustmentReason;
use App\Models\StockManagement\StockRequest;
use App\Models\StockManagement\Stock;
use App\Models\StockManagement\StockRequestItem;
use App\Models\StockManagement\Warehouse;
use App\Models\UserManagement\Admin;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Throwable;

trait Destockable{

    use CanAdjustStock;

    /** Debite le stock du produit
     * @param Warehouse $warehouse
     * @param float $qty
     * @param OrderProduct|StockRequestItem|Admin $adjuster
     * @throws Throwable
     */
    public function reduceStock(Warehouse $warehouse, $qty , $adjuster){
        $dir_log = "stocks/reduce-stock";
        $log_name = "check-stock";
        $product_id = $this->id;

        /*tous les outputs disponibles de ce produit dans l'entrepot*/
        $stocks = $warehouse->stocks() // déja ordonné par date de peremtion
                            ->availables()  // disponible
                            ->noneExpired()  // qui ne sont pas expirée
                            ->where('product_id',$product_id)
                            ->lockForUpdate()
                            ->orderBy('qty','asc') // du plus petit au plus grand
                            ->get();

        $stock_exist = filled($stocks); // (bool)
        $nom_produit = $this->fullName;
        $cmd = $adjuster->order_id ?? "";

        //si le produit a au moins 01 lot disponible
        if ($stock_exist){
            // qté total disponible du produit
            $total_qty = $stocks->sum("qty"); // [103 = [1,2,100]]

            // si la qté total dispo est >= qté commandé
            if ($total_qty >= $qty){
                $reste_a_debiter = $qty;
                $stop = false;
                $total_debiter = 0;

                //pour chaque lot de ce produit dans l'entrepot
                foreach ($stocks as $stock){
                    /**
                     * qty en stock disponible du lot
                     */
                    $qty_dispo_lot = $stock->qty ?? 0;
                    $total_debiter +=  $qty_dispo_lot;

                    /*si la qte dispo du lot >= à la qté commandée */
                    if ($total_debiter >= $qty) {

                        /**
                         * $qty_dispo_lot= 100 ; $qty= 4; $qty_total_debiter=(100 +3)=103;
                         *  $qty_debiter_lot=3 =; $qty_restante = 100 - 3) (103 >= 4)
                         * qté debité = reste a  debité
                         */
                        //
                        $qty_debiter_lot =  $reste_a_debiter; //

                        /**
                         * qté restante = (qté en stock - reste a  debité)
                         */
                        $qty_restant_lot = $qty_dispo_lot - $reste_a_debiter;
                        $reste_a_debiter = 0;
                        $stop = true;
                    }
                    else{

                        /**
                         * $qty_dispo_lot== 1 ; $qty= 4; $qty_total_debiter=(1 +0)= 1;
                         * $qty_debiter_lot=1; $reste_a_debiter = 4 - 1 = 3 ; (1 >= 4)
                         * $qty_dispo_lot= 2 ; $qty= 4; $qty_total_debiter=(1 + 2)= 3;
                         * $qty_debiter_lot=2 ; $reste_a_debiter = 3 - 2 =1  (3 >= 4)
                         */
                        $qty_debiter_lot = $qty_dispo_lot;

                        /**
                         *  reste à debité = soustrait qte_dipo_lot
                         */
                        $reste_a_debiter -= $qty_dispo_lot;

                        $qty_restant_lot  = $qty_dispo_lot - $qty_debiter_lot; //
                    }

                    $c = [
                        "id_stock" => $stock->id,
                        "product_id" => $stock->product_id,
                        "qty_total_dispo" => $total_qty,
                        "id_lot" => $stock->lot_id,
                        "qty_dispo_lot" => $qty_dispo_lot,
                        "qte_debiter_sur_lot" => $qty_debiter_lot,
                        "qty_restant_lot" => $qty_restant_lot,
                        "qte_cmde" => $qty,
                        "total_debiter" => $total_debiter,
                        "reste_a_debiter" => $reste_a_debiter,
                        "cmd" => $cmd,
                        "model" => $adjuster->getMorphClass(),
                    ];

                    write_log($dir_log,json_encode($c),'info',$log_name);

                    if ($qty_debiter_lot > 0){

                        $comment = "----";

                        $adjustement_data = [
                                                "operation_code"  => AdjustmentOperation::OUTPUT,
                                                "qty_adjusted" => $qty_debiter_lot,
                                                "adjuster" => $adjuster,
                                            ];

                        $reason = AdjustmentReason::NOTING ;

                        //si c'est une demande
                        if ($adjuster instanceof StockRequest){
                            $sender = $adjuster->requester ; // PointDeVente
                            $receiver = $adjuster->receiver ; // Magasin
                            $comment = __("Request of :qty unit(s) initiated by :user , from :sender to :receiver at :date",
                                ['qty' => format_amount($qty_debiter_lot), "user" => $adjuster->createdBy->full_name ,
                                    "receiver" => $receiver->name , "sender" => $sender->name ,
                                    "date" => $adjuster->created_at->format(__("Y-m-d H:i:s"))]);

                            $reason = AdjustmentReason::STOCK_REQUEST;

                          //  throw_if(!$ajustement['is_success'],new CheckStockProductWhenOrderingException($ajustement['message']));

                        }

                        if ($adjuster instanceof OrderProduct){
                            $order = $adjuster->order ; // Order
                            $comment = __("Order of :qty unit(s) for oder id : ':order_id', by :customer , at :date",
                                        [
                                            'qty' => format_amount($qty_debiter_lot),
                                            "customer" => $order->customer->full_name ,
                                            "order_id" => $order->ref_id ,
                                            "date" => $order->created_at->format(__("Y-m-d H:i:s"))
                                        ]);

                            $reason = AdjustmentReason::ORDERED_PRODUCT;

                            //  throw_if(!$ajustement['is_success'],new CheckStockProductWhenOrderingException($ajustement['message']));

                        }

                       // $stock->decrement("qty",$qty_debiter_lot);

                        $adjustement_data['comment'] = $comment;
                        $adjustement_data["reason_code"] = $reason;


                        /**
                         * Débite le stock rattaché à cette ligne
                         * Cree un ajustement en 'Sortie'
                         */
                        $adjustement =  $stock->adjust($adjustement_data);
//                        dd($adjustement,$stock);
                    }
                    if ($stop){ break; }
                }

                if ($adjuster instanceof StockRequest){
                    $sum_debited_qty = $adjuster->outputs->sum('debited_qty');
                    $message = __("Erreur technique imprévue sur :produit",
                        ['sum' => format_amount($sum_debited_qty),"qty" => format_amount($qty), "produit" => $this->fullName]);
                    throw_if(($sum_debited_qty != $qty), new \Exception($message));
                }
            }
            else{
                $m = __(":nom est indisponible pour valider cette opération",['nom' => $nom_produit]);
                write_log($dir_log,$m,'error',$log_name);
                throw new CheckStockProductWhenOrderingException($m);
            }
        }
        else{
            $m = __(":nom n'a pas de stock",['nom' => $nom_produit]) ;
            write_log($dir_log,$m,'error',$log_name);
            throw new CheckStockProductWhenOrderingException($m);
        }
   }

    /**
     * @param Warehouse $warehouse
     * @param $qty
     * @return array
     */
    public function isAvailable(Warehouse $warehouse, $qty = 1){
        $product_id = $this->getKey();

        //tous les lots de ce produit dans le pv
        $stocks =  $warehouse->stocks()
                             ->availables() // les qte > 0
                             ->noneExpired() // qui ne sont expirés
                             ->where('product_id',$product_id)
                             ->get();

        //qte totale de ce produit dans l'entrepot
        $available_qty = $stocks->sum("qty") ?? 0;

        //disponible si qte total >= qte cmd
        $available = ($available_qty >= $qty);

        return [
            'available' => $available,
            'available_qty' => $available_qty,
        ];
    }


    /** Les stocks l'és au produit
     * @return HasMany
     */
    public function stocks(){
        return $this->hasMany(Stock::class,"product_id");
    }

    /** Les entrepots dans lesquelles ont trouve le produit
     * @return mixed
     */
    public function warehouses(){
        return $this->belongsToMany(
            Warehouse::class, // le model/table qu'on veut retourner (cities)
            (new Stock)->getTable(), // le model/table intermédiare (states)
            'product_id', // states.country_id == countries.id
            'warehouse_id' // cities.state_id = states.id
        );
    }
}
