<?php

// ##############################################################
use KupShop\AdminBundle\Admin\UserMessagesAdminInterface;
use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\AdminBundle\Util\Filter\OrdersFilterSpecs;
use KupShop\BalikonosBundle\Exception\BalikonosException;
use KupShop\KupShopBundle\Context\ContextManager;
use KupShop\KupShopBundle\Context\CountryContext;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\DomainContext;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Email\EmailGroupTypeEnum;
use KupShop\KupShopBundle\Email\OrderMessageEmail;
use KupShop\KupShopBundle\Email\UserMessagesInterface;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Functional\Mapping;
use KupShop\KupShopBundle\Util\Logging\Initiator;
use KupShop\KupShopBundle\Util\Mail\SMSSender;
use KupShop\KupShopBundle\Util\Price\Price;
use KupShop\OrderingBundle\Event\OrderEvent;
use KupShop\OrderingBundle\Event\OrderMergeEvent;
use KupShop\OrderingBundle\Exception\PaymentException;
use KupShop\OrderingBundle\Util\Attachment\AttachmentLocator;
use KupShop\OrderingBundle\Util\Order\OrderInfo;
use Query\Operator;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

// ##############################################################

$main_class = 'AdminOrders';

class AdminOrders extends Window implements UserMessagesAdminInterface
{
    protected $template = 'window/orders.tpl';
    protected $tableName = 'orders';
    protected $show_on_web = 'order';
    protected $nameField = 'order_no';

    /** @required */
    public ContextManager $contextManager;

    protected ?\Order $oldOrder = null;

    protected function getItemsOrdering()
    {
        return 'oi.id';
    }

    public function get_vars()
    {
        global $cfg;

        $vars = parent::get_vars();
        $ID = $this->getID();
        $acn = $this->getAction();
        $this->contextManager = ServiceContainer::getService(\KupShop\KupShopBundle\Context\ContextManager::class);

        $pageVars = getVal('body', $vars);
        $pageVars['data']['total_price'] = toDecimal($pageVars['data']['total_price'] ?? 0);

        if (!isset($Submit)) {
            if ($acn == 'add') {
                $pageVars['data']['status'] = 0;
                $pageVars['data']['date_created'] = date('Y-m-d H:i:s');

                $pageVars['data']['status_payed'] = 0;
                $pageVars['data']['status_dispatch'] = 0;
                $pageVars['data']['status_storno'] = 0;
            }
        }

        $currencyContext = ServiceContainer::getService(CurrencyContext::class);

        $order = new Order($ID);
        if (!$order->createFromDB($ID)) {
            // Set default contexts when order does not exists
            $order->currency = $currencyContext->getDefaultId();
            $order->id_language = ServiceContainer::getService(LanguageContext::class)->getDefaultId();
            $order->delivery_country = ServiceContainer::getService(CountryContext::class)->getActiveId();
        }

        $currencyContext->activate($order->currency ?? $currencyContext->getDefaultId());

        $pageVars['data']['domain'] = $this->contextManager->activateOrder($order, function () {
            return Contexts::get(DomainContext::class)->getActiveWithScheme();
        });

        $pageVars['data']['complete_order'] = true;
        if (!empty($ID)) {
            $pageVars['data']['total_price_without_vat'] = toDecimal('0');

            $qb = sqlQueryBuilder()
                ->select('oi.id, oi.id_product, oi.id_variation, oi.pieces, oi.piece_price, oi.total_price, oi.descr, oi.tax, oi.note as item_note, oi.discount,
	           		COALESCE(pv.in_store, p.in_store) in_store, COALESCE(pv.ean, p.ean) ean')
                ->from('order_items', 'oi')
                ->leftJoin('oi', 'products', 'p', 'p.id=oi.id_product')
                ->leftJoin('p', 'products_variations', 'pv', 'pv.id=oi.id_variation')
                ->where(\Query\Operator::equals(['oi.id_order' => $ID]))
                ->orderBySql($this->getItemsOrdering());

            if (findModule(Modules::PRODUCTS_VARIATIONS, Modules::SUB_CODE)) {
                $qb->addSelect('COALESCE(pv.code, p.code) code');
            } else {
                $qb->addSelect('p.code code');
            }

            if (findModule(Modules::PRODUCTS, Modules::SUB_NOTE)) {
                $qb->addSelect('COALESCE(pv.note, p.note) note_');
            } else {
                $qb->addSelect('\'\' as note_');
            }

            if (findModule(Modules::STORES)) {
                $qb->andWhere(\KupShop\StoresBundle\Query\StoresQuery::addStoresInStoreAmounts());
                $qb->groupBy('oi.id');
            }

            if (findModule(Modules::PRODUCTS, Modules::SUB_PRICE_BUY)) {
                $qb->addSelect('oi.price_buy price_buy');
            }

            $oop = [];
            foreach ($qb->execute() as $key => $item) {
                $product = new Product($item['id_product']);
                $item['ID'] = $item['id'];
                $item['discount'] = toDecimal($item['discount']);
                $item['Price'] = formatPrice($item['total_price'], $item['tax']);
                $item['PiecePrice'] = formatPrice($item['piece_price'], $item['tax']);
                $item['pieces'] = floatval($item['pieces']);
                $item['in_store'] = floatval($item['in_store']);
                $pageVars['data']['total_price_without_vat'] = $pageVars['data']['total_price_without_vat']->add(toDecimal($item['total_price']));

                if (!empty($item['item_note'])) {
                    $item['custom_data'] = $product->parseNote($item['item_note']);
                    $item['item_note'] = $product->printNote($product->parseNote($item['item_note']));
                }

                if ($item['in_store'] < 0) {
                    if ($item['id_product']) {
                        // Currently ordered offset
                        $query = 'SELECT SUM(oi.pieces)
								FROM '.getTableName('order_items').' oi
								LEFT JOIN '.getTableName('orders').' o ON oi.id_order=o.id,
								'.getTableName('orders')." o2
								WHERE o2.id={$ID} AND o.date_created >= o2.date_created AND oi.id_product={$item['id_product']}
								AND o.status_storno=0 AND o.id!={$ID} AND o.status IN (".join(',', getStatuses('active')).')';
                        if (!empty($item['id_variation'])) {
                            $query .= "AND oi.id_variation={$item['id_variation']}";
                        }
                        $item['piecesOrdered'] = returnSQLResult($query);

                        if (($item['piecesOrdered'] + $item['in_store']) < 0) {
                            $pageVars['data']['complete_order'] = false;
                        }
                        // Suppliers products
                        if (findModule('products_suppliers')) {
                            $query = 'SELECT SUM(in_store)
										FROM '.getTableName('products_of_suppliers')." pos
										WHERE pos.id_product={$item['id_product']}";

                            if (!empty($item['id_variation'])) {
                                $query .= " AND pos.id_variation={$item['id_variation']}";
                            }

                            $item['piecesInSuppliers'] = returnSQLResult($query);
                        }
                    }
                }

                if (!empty($item['id_product']) && empty($item['id_variation']) && $product->hasVariations()) {
                    $item['class'] = 'row-red';
                    $item['variation_title'] = '!Neexistujici varianta!';
                }

                $item['img'] = createScriptURL([
                    's' => 'redir',
                    'type' => 'productImage',
                    'id' => $item['id_product'],
                    'id_variation' => $item['id_variation'],
                ]);

                if ($item['id_product'] && (findModule(Modules::PRODUCTS_SUPPLIERS) || findModule(Modules::ORDERS_OF_SUPPLIERS))) {
                    $query = 'SELECT pos.in_store, pos.code, s.order_url, s.name, pos.price_buy, v.vat
							  FROM '.getTableName('products_of_suppliers').' pos
							  LEFT JOIN '.getTableName('suppliers').' s ON pos.id_supplier = s.id
							  LEFT JOIN '.getTableName('products').' AS p ON pos.id_product=p.id
							  LEFT JOIN '.getTableName('vats')." AS v ON v.id=p.vat
							  WHERE pos.id_product={$item['id_product']}";
                    if (!empty($item['id_variation'])) {
                        $query .= " AND pos.id_variation={$item['id_variation']}";
                    }

                    $query .= ' ORDER BY s.id';

                    $item['suppliers'] = [];
                    foreach (sqlQuery($query) as $supplier) {
                        $item['suppliers'][] = $supplier;
                    }
                }

                if (findModule(Modules::STOCK_IN)) {
                    $sql = sqlQuery("SELECT sii.quantity as future_stock_in_pieces, sii.id_product, sii.id_variation, si.date_issued, s.name
                        FROM stock_in_items sii
                        JOIN stock_in si ON si.id = sii.id_stock_in AND si.id_index ='future'
                        LEFT JOIN suppliers s ON si.id_supplier=s.id
                        WHERE sii.id_product=:id_product AND ((sii.id_variation IS NULL AND :id_variation IS NULL) OR sii.id_variation=:id_variation)",
                        ['id_product' => $item['id_product'], 'id_variation' => $item['id_variation']]);
                    foreach (sqlFetchAll($sql) as $stock_in) {
                        $diff = null;
                        if ($stock_in['date_issued']) {
                            $dateIssued = DateTime::createFromFormat('Y-m-d', $stock_in['date_issued']);
                            $diff = (new DateTime())->diff($dateIssued);
                            $diff = (int) $diff->format('%r%a');
                        }

                        $item['stock_in'][] = [
                            'future_stock_in_pieces' => $stock_in['future_stock_in_pieces'],
                            'date_issued' => $stock_in['date_issued'],
                            'name' => $stock_in['name'],
                            'stock_in_days' => $diff,
                        ];
                    }

                    $sql = sqlQuery("SELECT sii.quantity as preorder_stock_in_pieces, sii.id_product, sii.id_variation, si.date_issued, s.name
                        FROM stock_in_items sii
                        JOIN stock_in si ON si.id = sii.id_stock_in AND si.id_index ='preorder'
                        LEFT JOIN suppliers s ON si.id_supplier=s.id
                        WHERE sii.id_product=:id_product AND ((sii.id_variation IS NULL AND :id_variation IS NULL) OR sii.id_variation=:id_variation)",
                        ['id_product' => $item['id_product'], 'id_variation' => $item['id_variation']]);
                    foreach (sqlFetchAll($sql) as $stock_in) {
                        $diff = null;
                        if ($stock_in['date_issued']) {
                            $dateIssued = DateTime::createFromFormat('Y-m-d', $stock_in['date_issued']);
                            $diff = (new DateTime())->diff($dateIssued);
                            $diff = (int) $diff->format('%r%a');
                        }

                        $item['preorder_stock_in'][] = [
                            'preorder_stock_in_pieces' => $stock_in['preorder_stock_in_pieces'],
                            'date_issued' => $stock_in['date_issued'],
                            'name' => $stock_in['name'],
                            'stock_in_days' => $diff,
                        ];
                    }
                }
                $oop[$key] = $item;
            }

            $pageVars['oop'] = $oop;

            if (findModule('order_payment')) {
                $pageVars['paid'] = toDecimal($order->getPayments());
            }

            if (!empty($pageVars['data']['invoice_email'])) {
                $pageVars['multipleOrder'] = returnSQLResult('SELECT o.id FROM orders o WHERE o.id!=:id AND o.status IN (:statuses) AND o.invoice_email=:email',
                    ['id' => $ID, 'statuses' => getStatuses('active'), 'email' => $pageVars['data']['invoice_email']],
                    ['statuses' => \Doctrine\DBAL\Connection::PARAM_INT_ARRAY]);
            }

            $SQL = sqlQuery("SELECT oh.id_status, oh.comment, oh.notified, a.login, DATE_FORMAT(oh.date, '{$GLOBALS['dbcfg']['date_format']} {$GLOBALS['dbcfg']['time_format']}') as datef, custom_data
							FROM ".getTableName('orders_history').' oh
							 LEFT JOIN'.getTableName('admins')." a ON oh.admin=a.id
							WHERE oh.id_order='".$ID."'
							ORDER BY oh.date ASC");
            $pageVars['orders_history'] = sqlFetchAll($SQL);

            $attachmentLocator = ServiceContainer::getService(AttachmentLocator::class);
            foreach ($pageVars['orders_history'] as &$history) {
                $sentAttachments = [];
                if (!empty($history['custom_data'])) {
                    $customData = json_decode($history['custom_data'], true);
                    $history['custom_data'] = $customData;
                    if (isset($customData['initiator'])) {
                        $history['custom_data']['initiator'] = Initiator::from($customData['initiator']);
                    }
                    if (!empty($customData['attachments'])) {
                        foreach ($customData['attachments'] as $type) {
                            $sentAttachments[] = $attachmentLocator->getAttachments(EmailGroupTypeEnum::ORDER)[$type];
                        }
                    }
                    $history['attachments'] = join(', ', $sentAttachments);
                }
            }
            $pageVars['attachments_types'] = json_encode($attachmentLocator->getAttachments(EmailGroupTypeEnum::ORDER));
            $pageVars['attachments_urls'] = [];
            if ($this->getID()) {
                foreach ($attachmentLocator->getAttachments(EmailGroupTypeEnum::ORDER) as $type => $name) {
                    $pageVars['attachments_urls'][$type] = path('kupshop_admin_emailattachment_adminattachment',
                        ['id_order' => $this->getID(), 'type' => $type]);
                }
            }
            $pageVars['attachments_urls'] = json_encode($pageVars['attachments_urls']);

            // User price level
            if (findModule(Modules::PRICE_LEVELS)) {
                if ($orderPriceLevel = $order->getOrderPriceLevel()) {
                    $pageVars['price_level'] = \KupShop\KupShopBundle\Wrapper\PriceLevelWrapper::wrap($orderPriceLevel);
                }
            }

            $orderInfo = ServiceContainer::getService(OrderInfo::class);
            $used_order_discounts = $orderInfo->getUsedDiscounts($order->id);
            $pageVars['data']['used_order_discounts'] = $used_order_discounts;

            $pageVars['data']['packages'] = $orderInfo->getPackages($order);
        }

        $pageVars['printSetOffer'] = [
            'common' => [
                'Prodejka' => 'Prodejky',
            ],
            'products' => [
                'product' => 'Štítky produktů',
            ],
        ];

        $pageVars['userInfo'] = $order->getUser();
        if (!$pageVars['userInfo']) {
            $pageVars['userInfo'] = $this->selectSQL('users', ['email' => $order->getUserEmail()], ['id', 'note'])->fetch();
        }
        if ($order) {
            $pageVars['custom_data'] = $order->getDataAll();
        }

        $pageVars['delivery_editable'] = true;
        if ($deliveryType = $order->getDeliveryType()) {
            $pageVars['delivery_editable'] = $deliveryType->getDelivery()->requiresDeliveryAddress();
            if (($payment = $deliveryType->getPayment()) && $payment->getSelectedMethodId()) {
                $contextManager = ServiceContainer::getService(ContextManager::class);
                $pageVars['payment_method'] = $contextManager->activateOrder($order, function () use ($payment) {
                    $payment->loadConfig(Contexts::get(LanguageContext::class)->getActiveId());

                    return $payment->getSelectedMethod();
                });
            }
        }

        $pageVars['data']['flags_array'] = explodeFlags($pageVars['data']['flags']);

        /** @var SMSSender $smsSender */
        $smsSender = ServiceContainer::getService(SMSSender::class);
        $pageVars['SMSSenderBackend'] = $smsSender->getBackend();

        return array_merge($vars, [
            'type' => 'orders',
            'body' => $pageVars,
            'order' => $order,
        ]);
    }

    public function getPrintAttachments()
    {
        $order = $this->prepareOrder();
        $attachments = [];

        $attachmentLocator = ServiceContainer::getService(AttachmentLocator::class);
        foreach ($attachmentLocator->getAttachments(EmailGroupTypeEnum::ORDER) as $type => $name) {
            if (($type == 'order_invoice') && findModule(Modules::ORDERS, Modules::SUB_INVOICES_FORBIDDEN)) {
                continue;
            }

            $attachments[$type] = $name;
        }

        return $attachments;
    }

    public function getData()
    {
        global $cfg, $dbcfg;

        $data = parent::getData();
        $acn = $this->getAction();
        $ID = $this->getID();

        // kontrola hodnot
        if (isset($data['date_created']) && empty($data['date_created'])) {
            $data['date_created'] = date('d-m-Y H:i:s');
        }
        if (isset($data['status']) && !is_numeric($data['status'])) {
            $data['status'] = 0;
        }
        if (isset($data['status_payed'])) {
            $this->prepareBoolean($data['status_payed']);
        }
        if (isset($data['status_dispatch']) && !is_numeric($data['status_dispatch'])) {
            $data['status_dispatch'] = 0;
        }
        if (isset($data['status_storno']) && !is_numeric($data['status_storno'])) {
            $data['status_storno'] = 0;
        }
        if (isset($data['id_user']) && intval($data['id_user']) <= 0) {
            $data['id_user'] = null;
        }

        if (!empty($data['date_created'])) {
            $data['date_created'] = $this->prepareDateTime($data['date_created']);
        }

        if (!empty($data['date_handle'])) {
            $data['date_handle'] = $this->prepareDateTime($data['date_handle']);
        }

        if (!empty($data['date_delivered'])) {
            $data['date_delivered'] = $this->prepareDateTime($data['date_delivered']);
        }

        if (!empty($data['date_accept'])) {
            $data['date_accept'] = $this->prepareDateTime($data['date_accept']);
        }

        if (!empty($data['date_due'])) {
            $data['date_due'] = $this->prepareDateTime($data['date_due']);
        }

        if (!empty($cfg['Order']['Flags'])) {
            $flags = getVal('flags', $data, []);

            $flags = join(',', $flags);

            $data['flags'] = $flags;
        }

        if (!empty($data['tab_selected'])) {
            if ($data['tab_selected'] === 'retail') {
                $data['invoice_name'] = translate('retailSale', 'orders', false, true);
            }
        }

        if (!empty($data['id_user']) && (empty($data['invoice_name']) && empty($data['invoice_email']))) {
            $this->injectUserData($data);
        }
        $delivers_fields = [
            'delivery_name' => 'invoice_name',
            'delivery_surname' => 'invoice_surname',
            'delivery_firm' => 'invoice_firm',
            'delivery_street' => 'invoice_street',
            'delivery_city' => 'invoice_city',
            'delivery_zip' => 'invoice_zip',
            'delivery_country' => 'invoice_country',
            'delivery_phone' => 'invoice_phone',
        ];
        if (empty($data['delivery_name']) && empty($data['delivery_surname'])
            && empty($data['delivery_firm']) && empty($data['delivery_street'])
            && empty($data['delivery_city']) && empty($data['delivery_zip']) && empty($_GET['data']['invoice_name'])
        ) {
            foreach ($delivers_fields as $delivery_fieldname => $invoice_fieldname) {
                if (!empty($data[$invoice_fieldname])) {
                    $data[$delivery_fieldname] = $data[$invoice_fieldname];
                }
            }
        }

        // getData se volá víckrát, a potřebujeme zajistit že máme fakt původní objednávku vč. custom data
        if (!$this->oldOrder && $ID) {
            $this->oldOrder = new Order();
            $this->oldOrder->createFromDB($ID);
            $this->oldOrder->getDataAll();
        }

        if (!empty($data['delivery_data']) && $ID) {
            $order = new Order();
            $order->createFromDB($ID);

            $delivery = $order->getDeliveryType()->getDelivery();

            $pointData = $delivery->storeDeliveryInfo($data['delivery_data']);
            $order->setData('delivery_data', $pointData);

            try {
                $delivery->applyToOrder($data, $order);
            } catch (InvalidArgumentException $e) {
                $this->returnError($e->getMessage());
            }
        }

        if (!empty($data['delivery']) && !empty($data['payment'])) {
            if ($deliveryTypeID = $this->selectSQL('delivery_type',
                ['id_delivery' => $data['delivery'], 'id_payment' => $data['payment']],
                ['id'])->fetchColumn()) {
                $deliveryType = DeliveryType::get($deliveryTypeID, true);
                $data['id_delivery'] = $deliveryTypeID;
                $data['delivery_type'] = $deliveryType->name;
            }
        } elseif (($data['delivery'] ?? false) === '0' && ($data['payment'] ?? false) === '0') {
            $data['id_delivery'] = null;
            $data['delivery_type'] = '';
        }

        if (getVal('Submit') && $acn == 'add') {
            $data['order_no'] = '_999999'; // .rand(0, 999999);

            $currencyContext = ServiceContainer::getService(CurrencyContext::class);
            $allCurrencies = $currencyContext->getAll();
            if (findModule('currencies') && isset($data['currency']) && isset($allCurrencies[$data['currency']])) {
                $data['currency_rate'] = $allCurrencies[$data['currency']]->getRate();
            }

            $countryContext = Contexts::get(CountryContext::class);
            if (!$data['delivery_country']) {
                $data['delivery_country'] = $countryContext->getDefaultId();
            }

            if (!$data['invoice_country']) {
                $data['invoice_country'] = $countryContext->getDefaultId();
            }
        }

        return $data;
    }

    public function injectUserData(&$data)
    {
        $userFields = array_merge([
            'ico' => 'invoice_ico',
            'dic' => 'invoice_dic',
            'email' => 'invoice_email',
            'currency' => 'currency',
            'id_language' => 'id_language',
        ],
            Mapping::mapKeys(User::getFields(), function ($_, $x) {
                return [$x, "invoice_{$x}"];
            }),
            Mapping::mapKeys(User::getFields(), function ($_, $x) {
                return ["delivery_{$x}", "delivery_{$x}"];
            })
        );

        $user = sqlQueryBuilder()
            ->select('*')
            ->from('users')
            ->where(Operator::equals(['id' => $data['id_user']]))
            ->execute()->fetchAssociative();

        if (!$user) {
            return false;
        }

        foreach ($user as $key => $field) {
            if (!empty($field) && isset($userFields[$key])) {
                $data[$userFields[$key]] = $field;
            }
        }

        if (!empty($user['prefer_transport']) && empty($data['delivery']) && empty($data['payment'])) {
            $deliveryType = DeliveryType::get($user['prefer_transport'], true);
            $data['delivery'] = $deliveryType->id_delivery;
            $data['payment'] = $deliveryType->id_payment;
        }

        return true;
    }

    public function handleUpdate()
    {
        $ID = $this->getID();
        $acn = $this->getAction();
        $data = $this->getData();

        // kontrola jestli objednavka nebyla stornovana
        $storno = ReturnSQLResult('SELECT status_storno 
										FROM '.getTableName('orders')." 
										WHERE id='{$ID}'");
        if (intval($storno) == 1 && !$this->isDuplicate()) {
            // Stornovanou objednávku již nelze upravovat
            $this->returnError(getTextString('orders', 'errorCanceledCantEdit'));
        }

        if ($this->isDuplicate()) {
            $OLD_ID = $ID;
            $data['order_no'] = '';
        }

        $error = 0;

        if (empty($error)) {
            $order = new Order();

            if ($acn == 'edit') {
                $order->createFromDB($ID);
            }
            $SQL = parent::handleUpdate();

            if ($SQL) {
                if ($acn == 'add') {
                    $ID = $this->getID();

                    // set date created
                    sqlQueryBuilder()
                        ->update('orders')
                        ->set('date_created', 'NOW()')
                        ->set('source', ':source')
                        ->setParameter('source', OrderInfo::ORDER_SOURCE_ADMIN)
                        ->where(Operator::equals(['id' => $ID]))
                        ->execute();

                    $order->createFromDB($ID);

                    if (!empty($data['id_invoice'])) {
                        $order->setData('id_invoice', $data['id_invoice']);
                    }

                    // dispatch order created event - order no is generated in event subscriber
                    $this->getEventDispatcher()->dispatch(new OrderEvent($order), OrderEvent::ORDER_CREATED);

                    addActivityLog(
                        ActivityLog::SEVERITY_NOTICE,
                        ActivityLog::TYPE_CHANGE,
                        sprintf(getTextString('orders', 'activityAdded'), $order->order_no)
                    );

                    if ($this->isDuplicate()) {
                        $old_order = new Order();
                        $old_order->createFromDB($OLD_ID);

                        $manager = ServiceContainer::getService(ContextManager::class);

                        $manager->activateOrder($order, function () use ($order, $old_order) {
                            $fields = [
                                'date_accept' => null,
                                'date_handle' => null,
                            ];

                            if (findModule('currencies')) {
                                $fields['currency'] = $old_order->currency;
                                $fields['currency_rate'] = $old_order->currency_rate;
                            }

                            if (findModule(Modules::TRANSLATIONS)) {
                                $fields['id_language'] = $old_order->id_language;
                            }

                            try {
                                $order->copyItems($old_order, false, dispatchEvent: true);
                            } catch (\Exception $e) {
                                $this->addError($e->getMessage());
                            }

                            $this->updateSQL('orders', $fields, ['id' => $order->id]);

                            if ($deliveryData = $old_order->getData('delivery_data')) {
                                $order->setData('delivery_data', $deliveryData);
                            }

                            if ($paymentData = $old_order->getData('payment_data')) {
                                $order->setData('payment_data', $paymentData);
                            }

                            $event = new OrderEvent($order);
                            $this->getEventDispatcher()->dispatch($event, OrderEvent::ORDER_COMPLETE);
                        });
                    }

                    $order->status = -1;
                } elseif ($acn == 'edit') {
                    $event = new OrderEvent($order);
                    $this->getEventDispatcher()->dispatch($event, OrderEvent::ORDER_EDITED);

                    addActivityLog(ActivityLog::SEVERITY_NOTICE, ActivityLog::TYPE_CHANGE,
                        sprintf(getTextString('orders', 'activityEdited'), $data['order_no']),
                        data: ActivityLog::addObjectData([$order->id => $order->order_no], 'orders')
                    );

                    if ($order->id_delivery != ($data['id_delivery'] ?? false) || ($this->oldOrder !== null && $this->oldOrder->getData('delivery_data', false) != $data['delivery_data'])) {
                        $order->setData('delivery_data_old', $this->oldOrder->getData('delivery_data', false));
                        $event = new OrderEvent($order);

                        $this->getEventDispatcher()->dispatch($event, OrderEvent::ORDER_DELIVERY_CHANGED);
                    }
                }

                foreach ($data['custom_data'] ?? [] as $key => $value) {
                    $order->setData($key, $value);
                }
                $smsService = ServiceContainer::getService(SMSSender::class);
                if ($smsService->getBackend() && !empty($data['send_telfa_sms'])) {
                    if (empty($data['invoice_phone'])) {
                        $this->returnError('Není vyplněno telefonní číslo příjemce sms!');
                    }

                    if (empty($data['comments'])) {
                        $this->returnError('Neodesláno - prázdná zpráva!');
                    }

                    $order = new Order($ID);
                    $order->createFromArray($data);

                    $message = $order->replacePlaceholders($data['comments']);

                    $res = $smsService->getBackend()->sendSMS($data['invoice_phone'], strip_tags($message));

                    if ($res !== true) {
                        $this->returnError('SMS se nepodařilo odeslat');
                    }

                    $smsService->saveSMStoOrder($ID, 'Na tel. '.$data['invoice_phone'].': '.$message, 2, $order->status);

                    $this->returnOK('SMS odeslána.');
                }

                // Custom data
                $this->saveCustomOrderData($data['data'] ?? []);

                // Change order status
                $force = null;
                $data['comments'] = trim($data['comments'] ?? '');
                if ($data['comments'] != '') {
                    $force = true;
                }
                if (!empty($data['doNotNotify']) || $acn == 'add') {
                    $force = false;
                }

                $order->changeStatus($data['status'], $data['comments'], $force, $data['status_comment']);

                if ($this->getErrors()) {
                    $this->returnError($this->getErrors()[0], true);
                }

                $this->returnOK(null, true);
            } else {
                $this->returnError($GLOBALS['txt_str']['status']['scripterror']);
            }
        }
    }

    public function saveCustomOrderData($data)
    {
        $order = $this->prepareOrder();
        if (!empty($data)) {
            foreach ($data as $key => $value) {
                $order->setData($key, $value);
            }
        }
    }

    public function handleDelete()
    {
        $ID = $this->getID();
        if (!findRight('ORDER')) {
            redirect('launch.php?s=error.php&id=1');
        }
        try {
            sqlGetConnection()->transactional(function () use ($ID) {
                $SQL = sqlQuery(
                    'SELECT order_no
                            FROM '.getTableName('orders')."
                            WHERE id='".$ID."' "
                );
                $order = sqlFetchArray($SQL);
                sqlFreeResult($SQL);

                writeDownActivity(sprintf(getTextString('orders', 'activityDeleted'), $order['order_no']));

                $SQL = sqlQuery(
                    'SELECT id_product, id_variation, pieces
                            FROM '.getTableName('order_items')."
                            WHERE id_order='".$ID."' AND id_product<>0 "
                );
                while (($row = sqlFetchArray($SQL)) !== false) {
                    $prod = new Product($row['id_product']);
                    $prod->sell($row['id_variation'], -$row['pieces']);
                }

                // ##########################################################
                // smazat polozky objednavky
                sqlQuery('DELETE FROM '.getTableName('order_items')." WHERE id_order='{$ID}' ");
                // smazat objednavku
                sqlQuery('DELETE FROM '.getTableName('orders')." WHERE id='{$ID}' ");

                return;
            });
            redirect('launch.php?s=orders.php&acn=erased');
        } catch (Exception $e) {
            if (strpos($e->getMessage(), 'return_items') !== false) {
                $this->returnError(translate('returnedItemsDeleteOrder'));
            }

            getRaven()->captureException($e);
        }
    }

    protected function getEventDispatcher(): EventDispatcherInterface
    {
        static $eventDispatcher;

        if (!$eventDispatcher) {
            $eventDispatcher = ServiceContainer::getService('event_dispatcher');
        }

        return $eventDispatcher;
    }

    protected function sendToBalikobot()
    {
        $data = $this->getData();

        $this->saveCustomOrderData($data['data'] ?? []);

        $balikobot = ServiceContainer::getService(\KupShop\BalikonosBundle\Balikobot::class);

        $order = $this->prepareOrder();
        $data = $order->getData('balikobot');

        try {
            if ($balikobot->hasMultipleCollectionPlaces() && !empty($data['user'])) {
                $balikobot->setCredentialsByName($data['user']);
            }

            $IDs[$this->getID()] = [
                'packages' => $data['packages'] ?? 1,
                'note' => $data['note'] ?? null,
                'size' => $data['size'] ?? null,
            ];

            $balikobot->setIDs($IDs);
            $balikobot->sendDeliveries();
        } catch (BalikonosException $e) {
            $this->returnError($e->getMessage());
        }

        return $balikobot;
    }

    public function handlePrintTicket()
    {
        try {
            $balikobot = $this->sendToBalikobot();

            if ($error = $balikobot->getError()) {
                $this->returnError($error);
            } else {
                $balikobot->printTickets(null, $this->getData()['data']['balikobot']['position'] ?? '1', '', [$this->getID()]);
            }
        } catch (BalikonosException $e) {
            $this->returnError($e->getMessage());
        }
    }

    public function handlePrintExistingTicket()
    {
        try {
            $data = $this->getData();
            $this->saveCustomOrderData($data['data'] ?? []);

            $balikobot = ServiceContainer::getService(\KupShop\BalikonosBundle\Balikobot::class);
            $balikobot->printTickets(null, $data['data']['balikobot']['position'] ?? '1', '', [$this->getID()]);
        } catch (BalikonosException $e) {
            $this->returnError($e->getMessage());
        }
    }

    public function handleSendToBalikobot()
    {
        $balikobot = $this->sendToBalikobot();

        if ($error = $balikobot->getError()) {
            $this->returnError($error);
        } else {
            $this->logBalikobotResult('Ručně nahráno do Balíkobota', $this->prepareOrder(), $balikobot);
            $this->returnOK('Úspěšně nahráno do Balíkobota');
        }
    }

    public function handleDeleteFromBalikobot()
    {
        $balikobot = ServiceContainer::getService(\KupShop\BalikonosBundle\Balikobot::class);
        try {
            foreach ($this->getBalikobot() as $item) {
                if ($item['close'] != 2) {
                    $balikobot->deletePackage($item['id'] ?? null);
                }
            }
            $this->logBalikobotResult('Ručně odebráno z Balíkobota', $this->prepareOrder(), $balikobot);
            $this->returnOK('Úspěšně odebráno z Balíkobota');
        } catch (BalikonosException $e) {
            $this->returnError($e->getMessage());
        }
    }

    private function logBalikobotResult(string $message, Order $order, KupShop\BalikonosBundle\Balikobot $balikobot): void
    {
        $error = $balikobot->getError();
        if ($error) {
            $this->returnError($error);
        }

        $result = $balikobot->getResult();
        $result = $result[0] ?? [];

        $id = $result['id_balikonos'] ?? '';
        $packages = implode(', ', $result['package_ids'] ?? []);
        $packageCount = count($result['package_ids'] ?? []);

        $order->logHistory("{$message}. Balíky ({$packageCount}): {$packages}, ID: {$id}");
    }

    public function getBalikobot()
    {
        return sqlQueryBuilder()->select('*')->from('balikonos')
            ->where(\Query\Operator::equals(['id_order' => $this->getID()]))
            ->orderBy('close', 'ASC')
            ->execute()->fetchAllAssociative();
    }

    public function changeDelivery($id_delivery, $id_order = null)
    {
        $order = $this->prepareOrder($id_order);

        /** @var \KupShop\OrderingBundle\OrderDelivery $orderDelivery */
        $orderDelivery = ServiceContainer::getService(\KupShop\OrderingBundle\OrderDelivery::class);

        try {
            $result = $orderDelivery->recalculateDelivery($order, $id_delivery);
            if ($result) {
                return true;
            }
        } catch (\KupShop\OrderingBundle\Exception\DeliveryException $e) {
            $this->returnError($e->getMessage());
        }

        return false;
    }

    public function handleChangeCurrency()
    {
        sqlGetConnection()->transactional(function () {
            sqlQueryBuilder()->select('currency')->from('orders')->where(Operator::equals(['id' => $this->getID()]))->forUpdate()->execute();
            $order = $this->prepareOrder();
            $id_currency = getVal('id_currency');
            if ($order->currency == $id_currency) {
                $this->returnOK('Ok', false);
            }

            $orderCurrencyConverter = ServiceContainer::getService(\KupShop\OrderingBundle\Util\Order\OrderCurrencyConverter::class);

            return $orderCurrencyConverter->changeOrderCurrency($order, $id_currency);
        }) ? $this->returnOK() : $this->returnOK('Nepodařilo se changeOrderCurrency.');
    }

    public function handleMerge()
    {
        $ID = $this->getID();
        $newID = getVal('id_merge');

        if (empty($newID)) {
            $this->returnError('Nezadáno číslo objednávky');
        }

        $order_old = $this->prepareOrder($ID);
        $order_new = $this->prepareOrder($newID);

        $this->mergeOrders($order_old, $order_new);

        $ErrStr = "Položky objednávky {$order_old->order_no} byly přesunuty do objednávky {$order_new->order_no}. Objednávka {$order_old->order_no} byla stornována.";

        $this->setID($newID);

        $this->returnOK($ErrStr);
    }

    /**
     * @param $order_old Order
     * @param $order_new Order
     *
     * @return bool
     */
    public function mergeOrders($order_old, $order_new)
    {
        $ID = $order_old->id;
        $newID = $order_new->id;

        $event = new OrderMergeEvent($order_old, $order_new);
        $event_result = $this->getEventDispatcher()->dispatch($event, OrderMergeEvent::BEFORE_MERGE);

        if ($error = $event_result->getError()) {
            $this->returnError($error['message'] ?? 'Objednávky nelze sloučit!');
        }

        $manager = ServiceContainer::getService(ContextManager::class);

        $manager->activateOrder($order_old, function () use ($newID, $ID) {
            $merged_items = sqlQueryBuilder()->select("GROUP_CONCAT(CONCAT(' <br>', pieces, ' x ', descr)) as items")->from('order_items')->where(\Query\Operator::equals(['id_order' => $ID]))->execute()->fetch()['items'];

            sqlQueryBuilder()->update('order_items')->set('id_order',
                $newID)->where(\Query\Operator::equals(['id_order' => $ID]))->execute();
            if (findModule(\Modules::ORDER_DISCOUNT)) {
                sqlQueryBuilder()->update('discounts_coupons')->set('id_order_used',
                    $newID)->where(\Query\Operator::equals(['id_order_used' => $ID]))->execute();
                sqlQueryBuilder()->update('discounts_coupons')->set('id_order_purchased',
                    $newID)->where(\Query\Operator::equals(['id_order_purchased' => $ID]))->execute();
            }

            if (findModule(Modules::EET)) {
                sqlQueryBuilder()->update('eet_sent_payment')->set('id_order',
                    $newID)->where(\Query\Operator::equals(['id_order' => $ID]))->execute();
            }

            $order_old = $this->prepareOrder($ID);
            $order_new = $this->prepareOrder($newID);
            // Stornuje se az niz, ale v ramci recalculate se zavola ORDER_PAID eventa a vygeneruje se faktura i pro tu nestornovanou, coz je blbe.
            $order_old->status_storno = 1;
            $order_old->recalculate(true);
            $order_new->recalculate(true);

            $payments = $order_old->getPaymentsArray();
            foreach ($payments as $payment) {
                $order_new->updateSQL('order_payments', ['id_order' => $newID], ['id' => $payment['id']]);
            }
            $order_old->updatePayments();
            $order_new->updatePayments();

            $comment = sprintf(translate_shop('mergeSuccessComment', 'order_msg'), $order_new->order_no);
            $order_old->storno(false, null, false); // don't send ORDER_STORNO email (customerNotified = false)
            // send ORDER_COMMENT email
            $order_old->sendEmail($comment, null, false, \KupShop\KupShopBundle\Email\OrderEmail::getType()); // type=ORDER_COMMENT

            $comment = "Přidány položky objednávky {$order_old->order_no}:".$merged_items;
            $comment .= ". <br>Objednávka {$order_old->order_no} byla stornována.";
            $order_new->logHistory($comment, false);
        });

        return true;
    }

    public function getDeliveryList()
    {
        $order = $this->prepareOrder();

        if ($order->id !== null) {
            $contextManager = ServiceContainer::getService(ContextManager::class);

            return $contextManager->activateOrder($order, function () {
                return DeliveryType::getDeliveries(true);
            });
        }

        return [];
    }

    public function getPaymentList()
    {
        $order = $this->prepareOrder();

        if ($order->id !== null) {
            $contextManager = ServiceContainer::getService(ContextManager::class);

            return $contextManager->activateOrder($order, function () {
                return DeliveryType::getPayments(true);
            });
        }

        return [];
    }

    public function getValidDeliveryTypes()
    {
        $deliveryTypes = DeliveryType::getAll(true);
        $return = [];

        $return['0-0'] = true;
        foreach ($deliveryTypes as $deliveryType) {
            $return[$deliveryType->id_delivery.'-'.$deliveryType->id_payment] = true;
        }

        return $return;
    }

    public function handleRefreshDeliveryType()
    {
        $data = $this->getData();

        // save old delivery data to database - needed for correct delivery type/store change
        if ($this->oldOrder !== null && ($oldData = $this->oldOrder->getData('delivery_data', false))) {
            $this->oldOrder->setData('delivery_data_old', $oldData);
        }

        if (!empty($data['id_delivery'])) {
            $this->changeDelivery($data['id_delivery']);
        }

        $this->forceUpdate();
    }

    public function getDeliveryTypeList($totalPrice)
    {
        $order = $this->prepareOrder();

        if ($order->id !== null) {
            $contextManager = ServiceContainer::getService(ContextManager::class);

            return $contextManager->activateOrder(
                $order,
                function () use ($totalPrice) {
                    return Order::getDeliveryTypeList($totalPrice, false, 0, true);
                }
            );
        }

        return [];
    }

    public function handleSendMail()
    {
        $order = $this->prepareOrder();

        if (in_array($order->status, getStatuses('handled'))) {
            $comment = sprintf(translate('commentSendInvoice', 'orders'), $order['order_no']);
            $log_comment = translate('activityInvoiceEmailSent', 'orders');
        } else {
            $comment = sprintf(translate('commentSendRecap', 'orders'), $order['order_no']);
            $log_comment = translate('activityRecapEmailSent', 'orders');
        }

        $order->logChange($log_comment, true);
        $order->sendEmail($comment, null, true, \KupShop\KupShopBundle\Email\OrderEmail::getType()); // type=ORDER_COMMENT

        $this->returnOK('Email odeslán');
    }

    public function handleAddUserNote()
    {
        $order = $this->prepareOrder();
        $note = getVal('note', null, '');

        $userId = $order->id_user;
        if (empty($userId)) {
            $userId = $this->selectSQL('users', ['email' => $order->getUserEmail()], ['id'])->fetchColumn();
            if (empty($userId)) {
                $userId = addUserEmail($order->getUserEmail());
            }
        }

        if (empty($userId)) {
            throw new RuntimeException('Cannot add user note! $userId is empty');
        }

        $this->updateSQL('users', ['note' => $note], ['id' => $userId]);

        $this->returnOK('K uživateli byla přidána poznámka.');
    }

    public function prepareOrder($ID = null)
    {
        if (!$ID) {
            $ID = $this->getID();
        }

        $order = new Order();
        $order->createFromDB($ID);

        return $order;
    }

    public function handlePayOrder()
    {
        $order = $this->prepareOrder();

        $contextManager = ServiceContainer::getService(ContextManager::class);
        $contextManager->activateOrder($order, function () use ($order) {
            $delivery_type = $order->getDeliveryType($order->getDeliveryId());
            $pay_method = $delivery_type?->payment_class?->getPayMethod() ?: 0;
            $remaining_payment = $order->getRemainingPayment();

            if (!$order->isActive()) {
                $this->returnOK('Stornována objednávka');
            }

            if ($order->isPaid()) {
                $this->returnOK('Již zaplaceno');
            }

            $title = 'Zaplaceno z administrace eshopu';
            if ($remaining_payment < 0) {
                $title = 'Vrácení částky: objednávka '.$order->order_no;
            }

            $newId = $order->insertPayment($remaining_payment, $title, null, true, $pay_method);

            if (($remaining_payment < 0) && ($payment_class = $delivery_type->payment_class ?? null)
                && $payment_class->enabledReturnPayment()) {
                try {
                    $payment_class->loadConfig($order->getLanguage());
                    $payment_class->setOrder($order);
                    $result = $payment_class->returnPayment($remaining_payment, null, $newId);
                    $message = translate('returnSucceed', 'orderPayment');
                    addActivityLog(ActivityLog::SEVERITY_SUCCESS, ActivityLog::TYPE_COMMUNICATION, $message,
                        ['amount' => $remaining_payment, 'order_id' => $order->id, 'result' => $result]);
                    $result['paymentClass'] = $payment_class->class;
                    $result['session'] = ($result['id'] ?? '').'_'.$newId;
                    $this->updateSQL('order_payments', ['payment_data' => json_encode($result)], ['id' => $newId]);
                    $this->returnOK($message);
                } catch (PaymentException $e) {
                    $this->deleteSQL('order_payments', ['id' => $newId]);

                    if ($e->getShortMessage() == 'Adyen') {
                        // the refund request was successfully received by Adyen - no result yet,
                        // because you receive the outcome of the refund request asynchronously, in a REFUND webhook.
                        addActivityLog(ActivityLog::SEVERITY_SUCCESS, ActivityLog::TYPE_COMMUNICATION, $e->getMessage(),
                            ['amount' => $remaining_payment, 'order_id' => $order->id]);
                        $this->returnOK($e->getMessage());
                    }

                    $this->returnError($e->getMessage());
                }
            }
        });

        $this->returnOK();
    }

    public function handleCopyUserData()
    {
        $order_data = $this->getData();

        if (!empty($order_data['id_user'])) {
            $data = $this->getUserData($order_data['id_user']);
            if ($data) {
                $this->updateSQL('orders', $data, ['id' => $this->getID()]);
            } else {
                $this->returnError('Vyskytla se chyba.');
            }
        }
        $this->returnOK('Aktualizováno');
    }

    public function handleGenerateInvoiceNo()
    {
        if (!findRight('ORDER_INVOICE')) {
            redirect('launch.php?s=error.php&id=1');
        }

        $order = $this->prepareOrder();
        if (!$order->isActive()) {
            $this->returnError('Stornovaná objednávka');
        }

        if ($invoice_no = $order->generateInvoiceNo()) {
            $order->logHistory("Ručně vygenována faktura {$invoice_no} k objednávce.");
            $this->returnOK('Objednávce vytvořena faktura '.$invoice_no);
        }
    }

    private function getUserData($id_user)
    {
        $SQL = $this->selectSQL('users', ['id' => $id_user])->fetchAll();
        if (count($SQL) == 1) {
            $user = $SQL[0];
            $fields = ['name' => 'invoice_name',
                'id' => 'id_user',
                'surname' => 'invoice_surname',
                'firm' => 'invoice_firm',
                'ico' => 'invoice_ico',
                'dic' => 'invoice_dic',
                'street' => 'invoice_street',
                'city' => 'invoice_city',
                'zip' => 'invoice_zip',
                'country' => 'invoice_country',
                'state' => 'invoice_state',
                'custom_address' => 'invoice_custom_address',
                'phone' => 'invoice_phone',
                'email' => 'invoice_email',
                'delivery_name' => 'delivery_name',
                'delivery_surname' => 'delivery_surname',
                'delivery_firm' => 'delivery_firm',
                'delivery_street' => 'delivery_street',
                'delivery_city' => 'delivery_city',
                'delivery_zip' => 'delivery_zip',
                'delivery_country' => 'delivery_country',
                'delivery_state' => 'delivery_state',
                'delivery_custom_address' => 'delivery_custom_address',
                'delivery_phone' => 'delivery_phone',
            ];
            $data = [];

            foreach ($user as $key => $field) {
                if (!empty($field) && isset($fields[$key])) {
                    $data[$fields[$key]] = $field;
                }
            }

            return $data;
        } else {
            return false;
        }
    }

    public function getEmail()
    {
        /* @var OrderMessageEmail $email */
        return ServiceContainer::getService(OrderMessageEmail::class);
    }

    public function hasRights($name = null)
    {
        switch ($name) {
            case Window::RIGHT_DELETE:
                return findRight('ORDER_ERASE');
            default:
                return parent::hasRights($name);
        }
    }

    protected function getObject()
    {
        $object = parent::getObject(); // muze vyhodit NotFoundHttpException v pripade spatneho ID

        if (findRight('ORDERS_RESTRICTION') && $this->getID()) {
            $qb = sqlQueryBuilder()
                ->select('o.id')
                ->from('orders', 'o')
                ->where(Operator::equals(['o.id' => $this->getID()]));

            $ordersFilterSpec = ServiceContainer::getService(OrdersFilterSpecs::class);
            if ($specs = $ordersFilterSpec->getSpecs(getAdminUser()['data']['ordersRestrictionFilter'] ?? [])) {
                $qb->andWhere($specs);
            }

            // pokud se mi nepodari nacist objednavku, tak nemam prava na jeji zobrazeni a nebo neexistuje, tak hodim 404
            if (!$qb->execute()->fetchOne()) {
                throw new AccessDeniedException(translate('orderNotFound', 'orders'));
            }
        }

        return $object;
    }

    public function getUserEmails(): UserMessagesInterface
    {
        return ServiceContainer::getService(OrderMessageEmail::class);
    }
}
