<?php

namespace App\Modules\Bookings;

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

class PartService
{
    /**
     * 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\Part $model
     *
     * @return void
     */
    public function validate(Part $part)
    {
        // Throw an exception if the task that the part is added to has already been invoiced or paid, or it is a completed order
        switch ($part->related_type) {
            case 'task':
                $task = Task::findOrFail($part->related_id);

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

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

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

                break;
        }
    }

    /**
     * Create a new part with the data given as parameter.
     *
     * @param  array $data
     *
     * @return App\Modules\Bookings\Part 
     */
    public function create(array $data)
    {
        return DB::transaction(function () use (&$data) {

            $data['vat'] = Config::get('settings.vat');

            $part = new Part($data);

            if (! $part->stock->unlimited && ! $part->stock->composite) {
                // Get the quantity in stock substracting the quantity required from other parts
                $stock_available = $part->stock->quantity_available();

                // If the quantity needed is more than the quantity available, order the remaining quantity
                if ($part->quantity > $stock_available) {
                    $part->quantity_on_order = $part->quantity - $stock_available;
                }
            }

            $this->validate($part);

            Event::dispatch('PartCreating', $part);

            if (! $part->save()) {
                throw new Exception($part->errors, 1);
            }

            if ($part->stock->composite) {
                foreach ($part->stock->composite_items()->get() as $component) {
                    $part->composite_items()->attach([$component->id => ['quantity' => $component->pivot->quantity]]);
                }
            }

            $part->load('creator', 'stock', 'composite_items');

            return $part;
        });
    }

    public function update(Part $part, array $data)
    {
        // If the part has been invoiced, paid or completed, throw an exception 
        $this->validate($part);

        return DB::transaction(function () use (&$part, &$data) {

            $oldPart = clone $part;

            // Fill the part with new data
            $part->fill($data);

            // If it is not a composite or unlimited product, update the quantity_on_order
            // Only auto-calculate if quantity_on_order was not explicitly provided in the data
            if (! $part->stock->unlimited && ! $part->stock->composite && !isset($data['quantity_on_order'])) {
                $stock_available = $part->stock->quantity_available();

                $quantity_on_order = -1 * ($stock_available + ($oldPart->quantity - $oldPart->quantity_on_order) - $part->quantity);

                $part->quantity_on_order = $quantity_on_order > 0 ? $quantity_on_order : 0;
            }

            // Keep a copy of old composite items
            $oldCompositeItems = $part->composite_items()->get();

            // Save composite items
            if (isset($data['composite_items'])) {
                // Check if the user is trying to add the part itself as a composite
                // creating an infite loop
                if (array_search($part->stock_id, Arr::pluck($data['composite_items'], 'id')) !== false) {
                    throw new Exception('You cannot add the same part as a composite item!');
                }

                $composite_items = $this->getCompositeItemsFromData($data);
                $part->composite_items()->sync($composite_items);
            }

            Event::dispatch('PartUpdating', [$part, $oldPart]);

            Event::dispatch('CompositePartsUpdated', [$part, $oldCompositeItems, $part->composite_items()->get()]);

            if ($part->save()) {
                $part->load('composite_items');
            }

            return $part;
        });
    }

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

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

        return $composite_items;
    }
}
