<?php

namespace Modules\NsGastro\Events;

use App\Classes\Hook;
use App\Classes\Output;
use App\Events\OrderProductAfterSavedEvent;
use App\Exceptions\NotAllowedException;
use App\Models\Order;
use App\Models\Product;
use App\Services\Helper;
use App\Services\ModulesService;
use App\Services\OrdersService;
use App\Services\ResetService;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\View;
use Modules\NsGastro\Crud\AreaCrud;
use Modules\NsGastro\Crud\CanceledProductCrud;
use Modules\NsGastro\Crud\KitchensCrud;
use Modules\NsGastro\Crud\ModifierGroupCrud;
use Modules\NsGastro\Crud\TableCrud;
use Modules\NsGastro\Models\ModifierGroup;
use Modules\NsGastro\Models\Order as GastroOrder;
use Modules\NsGastro\Models\OrderProduct;
use Modules\NsGastro\Models\OrderProductModifier;
use Modules\NsGastro\Models\OrderProductModifierGroup;
use Modules\NsGastro\Models\Table;
use Modules\NsGastro\Models\TableSession;
use Modules\NsGastro\Services\KitchenService;
use Modules\NsGastro\Services\ProductModifiersService;
use Modules\NsGastro\Services\RestaurantDemoService;
use Modules\NsGastro\Services\TableService;
use Modules\NsMultiStore\Events\MultiStoreWebRoutesLoadedEvent;
use Modules\NsMultiStore\Models\Store;

/**
 * Register Events
 **/
class NsGastroEvent
{
    protected $kitchenService;

    protected $orderService;

    public function __construct(
        KitchenService $kitchenService,
        OrdersService $orderService
    ) {
        $this->kitchenService = $kitchenService;
        $this->orderService = $orderService;
    }

    public static function loadWebRoutes(MultiStoreWebRoutesLoadedEvent $event)
    {
        ModulesService::loadModuleFile('NsGastro', 'Routes/web');
    }

    /**
     * registers a custom order
     * type to NexoPOS.
     *
     * @param  array  $types
     * @return array $types
     */
    public static function orderTypes($types)
    {
        $types['dine-in'] = [
            'identifier'    =>  'dine-in',
            'label'         =>  __m( 'Dine In', 'NsGastro' ),
            'icon'          =>  asset('modules/nsgastro/images/chair.png'),
            'selected'      =>  false,
        ];

        return $types;
    }

    public static function registerWipeableTables($tables)
    {
        ns()->option->set('gastro_ready_meals', 0 );

        return array_merge($tables, [
            'nexopos_gastro_areas',
            'nexopos_gastro_modifiers_groups',
            'nexopos_orders_products_modifiers',
            'nexopos_orders_products_modifiers_groups',
            'nexopos_gastro_tables',
            'nexopos_gastro_tables_history',
            'nexopos_gastro_tables_sessions',
            'nexopos_gastro_kitchens',
            'nexopos_gastro_kitchens_categories',
            'nexopos_gastro_tables_booking_history',
        ]);
    }

    public static function registerCrud($identifier)
    {
        switch ($identifier) {
            case 'ns.gastro-areas':             return AreaCrud::class;
            case 'ns.gastro-tables':            return TableCrud::class;
            case 'ns.gastro-kitchens':          return KitchensCrud::class;
            case 'ns.gastro-modifiers-groups':  return ModifierGroupCrud::class;
            case 'ns.gastro-canceled-products': return CanceledProductCrud::class;
            default: return $identifier;
        }
    }

    public static function registerFooterScript(Output $output)
    {
        $output->addView('NsGastro::pos.footer' );
    }

    /**
     * Ensure modifiers groups and modifiers are
     * correctly retreived with the form.
     *
     * @param  array  $form
     * @param  Product  $entity
     * @return array
     */
    public static function updateProductForm($form, $entity)
    {
        $options = Helper::toJsOptions(ModifierGroup::get(), ['id', 'name']);
        $form['variations'][0]['tabs']['restaurant'] = [
            'label'     =>  __m('Gastro', 'NsGastro'),
            'fields'    =>  [
                [
                    'label'         =>  __m('Skip Cooking', 'NsGastro'),
                    'type'          =>  'switch',
                    'name'          =>  'skip_cooking',
                    'options'       =>  Helper::kvToJsOptions([__m('No', 'NsGastro'), __m('Yes', 'NsGastro')]),
                    'value'         =>  $entity->skip_cooking ?? false,
                    'description'   =>  __m('Products with skip cooking option won\'t land at the kitchen and will be market as ready.', 'NsGastro'),
                ], [
                    'label'         =>  __m('Modifiers Groups', 'NsGastro'),
                    'type'          =>  'multiselect',
                    'name'          =>  'modifiers_groups',
                    'options'       =>  Helper::toJsOptions(ModifierGroup::get(), ['id', 'name']),
                    'value'         =>  $entity ? (json_decode($entity->modifiers_groups) ?? '') : '',
                    'description'   =>  __m('If the modifiers groups is provided, the current product shouldn\'t be saved as a modifiers. This means the modifier group field shouldn\'t be selected.', 'NsGastro'),
                ], [
                    'label'         =>  __m('Attached To Group', 'NsGastro'),
                    'type'          =>  'select',
                    'name'          =>  'modifiers_group_id',
                    'options'       =>  $options,
                    'value'         =>  $entity->modifiers_group_id ?? '',
                    'description'   =>  __m('By defining the modifier group, the current product will be saved as a modifier.', 'NsGastro'),
                ],
            ],
        ];

        return $form;
    }

    /**
     * Make sure products can't be modifier
     * and product at the same time.
     *
     * @param  array  $input
     * @param  array  $source
     * @param  Product  $product
     * @return array $input
     */
    public static function filterProductPostInput($input, $source)
    {
        if (! empty($source['restaurant']['modifiers_groups']) && ! empty($source['restaurant']['modifiers_group_id'])) {
            throw new NotAllowedException(__m('A product cannot be a modifier and a product that has modifiers at the same time.', 'NsGastro'));
        }

        $input['skip_cooking'] = isset($source['restaurant']['skip_cooking']) ? (bool) $source['restaurant']['skip_cooking'] : false;
        $input['modifiers_groups'] = isset($source['restaurant']['modifiers_groups']) ? json_encode($source['restaurant']['modifiers_groups']) : '';
        $input['modifiers_group_id'] = $source['restaurant']['modifiers_group_id'] ?? 0;

        if (empty($input['modifiers_group_id'])) {
            $input['gastro_item_type'] = 'product';
        } else {
            $input['gastro_item_type'] = 'modifier';
        }

        return $input;
    }

    /**
     * Make sure products can't be modifier
     * and product at the same time.
     *
     * @param  array  $input
     * @param  array  $source
     * @param  Product  $product
     * @return array $input
     */
    public static function filterProductPutInputs($input, $source, $product)
    {
        if (! empty($source['restaurant']['modifiers_groups']) && ! empty($source['restaurant']['modifiers_group_id'])) {
            throw new NotAllowedException(__m('A product cannot be a modifier and a product that has modifiers at the same time.', 'NsGastro'));
        }

        $input['skip_cooking'] = isset($source['restaurant']['skip_cooking']) ? (bool) $source['restaurant']['skip_cooking'] : false;
        $input['modifiers_groups'] = isset($source['restaurant']['modifiers_groups']) ? json_encode($source['restaurant']['modifiers_groups']) : '';
        $input['modifiers_group_id'] = $source['restaurant']['modifiers_group_id'] ?? 0;

        if (empty($input['modifiers_group_id'])) {
            $input['gastro_item_type'] = 'product';
        } else {
            $input['gastro_item_type'] = 'modifier';
        }

        return $input;
    }

    /**
     * add a new columns to the product crud
     *
     * @param  array  $columns
     * @return array $columns
     */
    public static function addNewColumn($columns)
    {
        return array_insert_after($columns, 'name', [
            'gastro_item_type'  =>  [
                'label'         =>  __m('Item Type', 'NsGastro'),
                '$direction'    =>  '',
                'width'         =>  '120px',
                '$sort'         =>  false,
            ],
        ]);
    }

    /**
     * Provide a label that clearly makes products
     * and modifiers to stand out on the product list.
     *
     * @param  Product  $entry
     * @return Product
     */
    public static function setProductActions($entry)
    {
        $entry->gastro_item_type = $entry->gastro_item_type === 'modifier' ?
        '<strong class="rounded-full px-3 py-1 go-bg-purple-400 go-text-white">'.__m('Modifier', 'NsGastro').'</strong>' :
        '<strong class="rounded-full px-3 py-1 go-bg-green-400 go-text-white">'.__m('Product', 'NsGastro').'</strong>';

        return $entry;
    }

    /**
     * Store modifiers when a product is being saved.
     *
     * @param  OrderProductAfterSavedEvent  $event
     * @return void
     */
    public static function storeModifiers(OrderProductAfterSavedEvent $event)
    {
        $storedGroups = [];
        $storedModifiers = [];

        if (isset($event->postData['modifiersGroups'])) {
            foreach ($event->postData['modifiersGroups'] as $rawGroup) {
                $group = OrderProductModifierGroup::find($rawGroup['id'] ?? null);

                if (! $group instanceof OrderProductModifierGroup) {
                    $group = new OrderProductModifierGroup;
                }

                $group->forced = $rawGroup['forced'];
                $group->multiselect = $rawGroup['multiselect'];
                $group->name = $rawGroup['name'];
                $group->order_product_id = $event->product->id;
                $group->modifier_group_id = $rawGroup['modifier_group_id'];
                $group->countable = $rawGroup['countable'];
                $group->save();

                foreach ($rawGroup['modifiers'] as $rawModifier) {
                    $modifier = OrderProductModifier::find($rawModifier['id'] ?? null);

                    if (! $modifier instanceof OrderProductModifier) {
                        $modifier = new OrderProductModifier;
                    }

                    $modifier->unit_price = $rawModifier['unit_price'];
                    $modifier->quantity = $rawModifier['quantity'];
                    $modifier->name = $rawModifier['name'];
                    $modifier->order_product_id = $event->product->id;
                    $modifier->unit_quantity_id = $rawModifier['unit_quantity_id'];
                    $modifier->unit_id = $rawModifier['unit_id'];
                    $modifier->modifier_id = $rawModifier['modifier_id'];
                    $modifier->order_product_modifier_group_id = $group->id;
                    $modifier->tax_value = 0;
                    $modifier->total_price = $rawModifier['total_price'];
                    $modifier->save();

                    $storedModifiers[] = $modifier->id;

                    // we should normaly deplete the material from here.
                }

                $storedGroups[] = $group->id;
            }
        }

        $event->product->cooking_status = $event->postData['cooking_status'] ?? 'pending';
        $event->product->cooking_note = $event->postData['cooking_note'] ?? '';
        $event->product->meal_placed_by = Auth::id();
        $event->product->meal_placed_by_name = Auth::user()->username;
        $event->product->save();

        /**
         * delete ressource that has been ignored
         * from the POS (in case the order is edited)
         */
        OrderProductModifier::whereNotIn('id', $storedModifiers)
            ->where('order_product_id', $event->product->id)
            ->delete();

        OrderProductModifierGroup::whereNotIn('id', $storedGroups)
            ->where('order_product_id', $event->product->id)
            ->delete();
    }

    /**
     * Will compute the order. This is necessary
     * if a product has been canceled.
     *
     * @param  KitchenAfterUpdatedOrderEvent  $event
     * @return void
     */
    public function computeOrder(KitchenAfterUpdatedOrderEvent $event)
    {
        $this->orderService->refreshOrder($event->order);
    }

    public static function injectModifiers($view, $orderProduct)
    {
        $modifiers = OrderProductModifier::where('order_product_id', $orderProduct->id)->get();

        return $view.View::make('NsGastro::receipts.modifiers', compact('modifiers'));
    }

    public static function addModifiersOnDefaultReceipt( $view, $orderProduct )
    {
        $modifiers = OrderProductModifier::where('order_product_id', $orderProduct->id)->get();

        return $view.View::make('NsGastro::receipts.default-modifiers', compact('modifiers'));
    }

    public static function orderTemplateMapping($arguments, Order $order)
    {
        $table = Table::find($order->table_id);
        $arguments['table_name'] = $table instanceof Table ? $table->name : __('N/A');

        return $arguments;
    }

    /**
     * we'll filter canceled products
     *
     * @param  Collection<OrderProduct>  $products
     * @return Collection<OrderProduct>
     */
    public static function filterCanceledProducts($products)
    {
        return $products->filter(function ($product) {
            return $product->cooking_status !== 'canceled';
        });
    }

    /**
     * We're adding here attributes that will be used
     * sale saving an order.
     */
    public static function addOrderAttributes( $attributes )
    {
        $attributes = array_merge( $attributes, [
            'table_id',
            'seats',
            'table_name',
            'area_id',
            'area_name',
        ]);

        return $attributes;
    }

    /**
     * Will add supported tags for printing
     *
     * @param array
     * @return array
     */
    public static function addSupportedTags($tags)
    {
        $tags[] = __m('{table_name} : Displays the table name', 'NsGastro');

        return $tags;
    }

    public static function handleResetActon($response, $data)
    {
        /**
         * @var string $mode
         * @var bool $create_sales
         * @var bool $create_procurements
         */
        extract($data);

        if ($mode === 'gastro_demo') {
            /**
             * @var ResetService
             */
            $resetService = app()->make(ResetService::class);
            $resetService->softReset();

            /**
             * @var RestaurantDemoService
             */
            $demoService = app()->make(RestaurantDemoService::class);
            $demoService->run($data);

            return [
                'status'    =>  'success',
                'message'   =>  __m('The restaurant demo has been enabled.', 'NsGastro'),
            ];
        }

        return $response;
    }

    /**
     * We'll define the order type
     *
     * @param  array  $types code
     * @return array $type label
     */
    public static function setOrderType($types)
    {
        $types[GastroOrder::TYPE_DINEIN] = __m('Dine In', 'NsGastro');

        return $types;
    }

    public static function filterUnitPrice( $unitPrice, $data )
    {
        if ( isset( $data[ 'modifiersGroups' ] ) ) {
            $modifiersPrice     =   collect( $data[ 'modifiersGroups' ] )->map( function( $modifierGroup ) {
                return collect( $modifierGroup[ 'modifiers' ] )->map( function( $modifier ) {
                    return $modifier[ 'total_price' ];
                });
            })->flatten()->sum();
    
            return ns()->currency->define( $unitPrice )->additionateBy( $modifiersPrice )->toFloat();
        }

        return $unitPrice;
    }
}
