<?php

namespace App\Modules\Bookings;

use Illuminate\Support\Facades\Log;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use App\Modules\Stock\Stock;

class BuildService
{
    /**
     * A reference to the Monolog.
     *
     * @var \Illuminate\Log\Writer
     */
    private $log;

    public function __construct(Log $log)
    {
        $this->log = $log;
    }

    /**
     * Checks to see if the related type has been invoiced
     * or paid, in which case throws an exception.
     *
     * @param  App\Modules\Bookings\Build $model
     *
     * @return void
     */
    public function validate(Build $build)
    {
        // Throw an exception if the task that the build is added to has already been invoiced or paid, or it is a completed order
        switch ($build->related_type) {
            case 'task':
                $task = Task::findOrFail($build->related_id);

                if ($task->invoiced) {
                    throw new Exception('This task has already been invoiced. You cannot modify the builds for it.');
                }

                if ($task->paid) {
                    throw new Exception('This task has already been paid. You cannot modify the builds for it.');
                }

                if ($task->type == 'order' && $task->completed) {
                    throw new Exception('It is not possible to add builds to a completed order.');
                }

                break;
        }
    }

    /**
     * Create a new build with the data given as parameter. The initial status is always "planning", so the items
     * will be added as planned_components until the build is marked as in_progress.
     *
     * @param  App\Modules\Bookings\Build 	$build
     * @param  array 						$data
     *
     * @return App\Modules\Bookings\Build 
     */
    public function create(Build $build, array $data)
    {
        return DB::transaction(function () use (&$build, &$data) {
            if ($build->stock) {
                $build->name = $build->stock->name;
                $build->price = $build->stock->price_excluding_vat;
                $planned_items = [];
                foreach ($build->stock->composite_items()->get() as $component) {
                    $planned_items[$component->id] = ['quantity' => $component->pivot->quantity];
                }
            } elseif (isset($data['planned_components'])) {
                $planned_items = $this->itemsToArray($data['planned_components']);
            }

            // Check if the new build is not being attached to a completed, paid or invoice task
            if (! $build->save()) {
                throw new Exception($build->errors, 1);
            }

            $build->planned_components()->attach($planned_items);

            return $build;
        });
    }

    public function update(Build $build, array $data)
    {
        return DB::transaction(function () use (&$build, &$data) {

            $oldBuild = clone $build;
            $build->fill($data);

            if ($oldBuild->status != $build->status) {
                $build = $this->updateStatus($build, $oldBuild);
            } else {
                if ($build->status == 'planning') {
                    $build = $this->updatePlannedBuild($build, $data);
                }

                if ($build->status == 'in_progress') {
                    $build = $this->updateInProgressBuild($build, $oldBuild, $data);
                }
            }

            $build->save();

            return $build;
        });
    }

    public function delete(Build $build)
    {
        return DB::transaction(function () use (&$build) {
            $this->validate($build);

            if ($build->delete()) {
                Event::dispatch('BuildDeleted', $build);
            }

            return $build;
        });
    }

    /**
     * Plucks all the composite items from the request input.
     *
     * @return array
     */
    protected function itemsToArray($items)
    {
        $items_array = [];

        foreach ($items as $value) {
            $items_array[$value['id']] = ['quantity' => $value['pivot']['quantity']];
        }

        return $items_array;
    }

    protected function updatePlannedBuild(Build $build, array $data)
    {
        $new_items = isset($data['planned_components']) ? $this->itemsToArray($data['planned_components']) : null;

        // If the planned items are changing, update them 
        if ($new_items && ! Build::samePlannedComponents($build, $new_items)) {
            $build->planned_components()->detach();
            $build->planned_components()->attach($new_items);
        }

        return $build;
    }

    protected function updateInProgressBuild(Build $build, Build $oldBuild, array $data)
    {
        if ($oldBuild->price != $build->price) {
            throw new Exception('The buil price can not be edited once you have confirmed the plan. If needed, change the build price from the stock section.', 1);
        }

        // If changing items or name it means, it needs to create a new build template
        if (isset($data['stock']['composite_items'])) {
            $new_items = $this->itemsToArray($data['stock']['composite_items']);
        }

        $different_items = isset($new_items) && ! Stock::sameItems($build->stock, $new_items);

        if ($different_items) {
            throw new Exception('It is not possible to edit a confirmed build.', 1);
        }

        //The code belong is done to allow change the build items once it is in progress. The previous 
        // avoid this case to happen, but we keep the code for future changes on it. 
        $different_name = $build->stock->name != $build->name;

        if ($different_items || $different_name) {
            if ($different_items) {
                $newStock = $this->createStockFromExisting($build->stock, null, $build->price, $new_items);
            } else {
                $newStock = $this->createStockFromExisting($build->stock, $build->name, $build->price);
            }

            $oldStock = $build->stock;
            $build->stock_id = $newStock->id;
            $build->name = $newStock->name;
            $build->load('stock');
            Event::dispatch('BuildTemplateCloned', [$oldStock, $newStock, $build]);
        } else {
            Event::dispatch('BuildUpdating', [$build, $oldBuild]);
        }

        return $build;
    }

    protected function updateStatus($build, $oldBuild)
    {
        if ($build->status == 'in_progress' && $oldBuild->status == 'planning') {

            // If the build has a diferent name than the original stock, create a new build template with the planned items and the name given
            if (! $build->stock || $build->name != $build->stock->name) {
                if (! $build->stock) {
                    $newStock = Stock::create([
                                'name'                   => $build->name,
                                'price_excluding_vat'    => $build->price,
                                'group_id'               => $build->group_id,
                                'type'                   => 'stock',
                                'approval_required'      => 0,
                                'composite'              => 1,
                            ]);

                    if (! $newStock->exists) {
                        throw new Exception('The name has already been taken. Please change the build template name before confirm it.', 1);
                    }

                    $newStock->composite_items()->attach($this->itemsToArray($build->planned_components()->get()));
                } else {
                    $newStock = $this->createStockFromExisting($build->stock, $build->name, $build->price, $this->itemsToArray($build->planned_components()->get()));
                }

                $build->stock_id = $newStock->id;
                $build->load('stock');
                Event::dispatch('BuildConfirmed', $build);

                return $build;
            }

            // If the plan has the same items than the original template, just change the status and delete the planned items. 
            if (Stock::sameItems($build->stock, $this->itemsToArray($build->planned_components()->get())) && $build->price === $build->stock->price_excluding_vat) {
                Event::dispatch('BuildConfirmed', $build);

                return $build;
            }

            throw new Exception('To create a new template build from this plan, a new name is necessary. The planned items or price are not the same than the original template.', 1);
        }

        if ($build->status == 'completed' && $oldBuild->status == 'in_progress') {
            if (count($build->ordered_components()->get())  > 0) {
                throw new Exception('Ha build that is waiting for parts can not be completed.', 1);
            }

            Event::dispatch('BuildCompleted', $build); // Create this event that will do a transaction to add the quantity to stock

            //If there is another build completed related to the same stock template, merge them
            $existing = Build::where('related_type', $build->related_type)->where('related_id', $build->related_id)->where('stock_id', $build->stock_id)->where('status', 'completed')->first();

            if ($existing) {
                $existing->quantity += $build->quantity;
                $build->delete();
                $build = $existing;
            }

            return $build;
        }

        throw new Exception('The status change done is not allowed.', 1);
    }

    protected function createStockFromExisting($original_stock, $new_name, $new_price, $new_items = null)
    {
        $newStock = Stock::duplicate($original_stock, $new_items == null);

        if ($new_name) {
            $newStock->name = $new_name;
        }
        if ($new_price) {
            $newStock->price_excluding_vat = $new_price;
        }

        $newStock->save();

        if ($new_items != null) {
            $newStock->composite_items()->sync($new_items);
        }

        return $newStock;
    }
}
