<?php

namespace KupShop\DropshipBundle\Transfer;

use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\DropshipBundle\TransferInterface;
use KupShop\KupShopBundle\Context\CountryContext;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Query\JsonOperator;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\OrderingBundle\Util\Order\OrderInfo;
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
use Query\Operator;

class MallTransfer extends AbstractTransfer implements TransferInterface
{
    use \DatabaseCommunication;

    protected static string $type = 'mall';
    protected static string $name = 'MALL';

    public const URL_API = 'https://mpapi.mallgroup.com/v1/orders/';

    public function in(array $config): void
    {
        if (!($xml = $this->loadXML())) {
            return;
        }

        $countryContext = Contexts::get(CountryContext::class);

        $countries = $countryContext->getAll();
        $defaultCurrency = Contexts::get(CurrencyContext::class)->getDefaultId();

        foreach ($xml->ORDER as $order) {
            if (!$this->isDropshipOrderValidToImport($order)) {
                continue;
            }

            $deliveryType = $this->getDeliveryType($order);
            $delivery_type = $order->DELIVERY_METHOD.' - '.$order->PAYMENT_TYPE;
            $id_delivery = null;
            if ($deliveryType) {
                $delivery_type = $deliveryType->name;
                $id_delivery = $deliveryType->id;
            }

            $deliveryPrice = $order->DELIVERY_PRICE + $order->COD_PRICE;
            $customer = $order->ADDRESS;

            $country = (string) $customer->COUNTRY;
            if (!($countries[$country] ?? false)) {
                $countries = array_keys($countries);
                $country = reset($countries);
            }

            $customer_name = explode(' ', (string) $customer->NAME);
            $name = array_shift($customer_name);
            $surname = implode(' ', $customer_name);

            $data = [
                'date_created' => (new \DateTime())->format('Y-m-d H:i:s'),
                'status' => 0,

                'id_delivery' => $id_delivery,
                'delivery_type' => $delivery_type,

                'flags' => 'DSM',

                'note_invoice' => (string) $order->ID,
                'note_admin' => json_encode([
                    'mall' => [
                        'order_id' => (string) $order->ID,
                        'cash_on_delivery' => (string) $order->COD,
                        'delivery_method_id' => (string) $order->DELIVERY_METHOD_ID,
                    ],
                ]),

                'source' => OrderInfo::ORDER_SOURCE_DROPSHIP,

                'invoice_name' => $name ?? '',
                'invoice_surname' => $surname ?? '',
                'invoice_email' => (string) $customer->EMAIL,
                'invoice_phone' => (string) $customer->PHONE,
                'invoice_street' => (string) $customer->STREET,
                'invoice_city' => (string) $customer->CITY,
                'invoice_zip' => (string) $customer->ZIP,
                'invoice_country' => $country,

                'delivery_name' => $name ?? '',
                'delivery_surname' => $surname ?? '',
                'delivery_street' => (string) $customer->STREET,
                'delivery_city' => (string) $customer->CITY,
                'delivery_zip' => (string) $customer->ZIP,
                'delivery_country' => $country,
                'currency' => $defaultCurrency,
            ];

            $orderObj = sqlGetConnection()->transactional(function () use ($order, $data, $deliveryPrice) {
                $orderObj = $this->createOrder($order, $data);
                // zaloguju dalsi informace o objednavce
                $orderObj->logHistory(implode('<br>', [
                    'Číslo Mall objednávky: '.(string) $order->ID,
                    'ID výdejního místa: '.(string) $order->DELIVERY_METHOD_ID,
                    'SHIP_DATE: '.(string) $order->SHIP_DATE,
                ]));

                $orderID = $orderObj->id;

                // prepare order items
                $items = $this->prepareItems($order->ITEMS);
                // add delivery item
                $items[] = $this->getDeliveryItem($deliveryPrice);
                if ($order->DISCOUNT > 0) {
                    // add discount item
                    $items[] = $this->getDiscountItem(-1 * $order->DISCOUNT);
                }

                // insert order items
                foreach ($items as $item) {
                    if ($item['id_product']) {
                        $product = new \Product();
                        $product->createFromDB($item['id_product']);
                        $product->sell($item['id_variation'], toDecimal($item['pieces'])->asInteger());
                    }

                    $item['id_order'] = $orderID;

                    $this->insertSQL('order_items', $item);
                    $item['id'] = sqlInsertId();

                    $this->itemCreatedEvent(
                        product: $product ?? null,
                        idVariation: (int) $item['id_variation'],
                        piecePrice: toDecimal($item['piece_price']),
                        pieces: toDecimal($item['pieces'])->asInteger(),
                        data: [
                            'row' => $item,
                            'items_table' => 'order_items',
                        ],
                        order: $orderObj
                    );

                    unset($product);
                }

                $orderObj->recalculate(round: false);

                return $orderObj;
            });

            $this->modifyInsertedOrder($orderObj, $order);
        }
    }

    protected function getDeliveryItem($deliveryPrice): array
    {
        $deliveryPrice = toDecimal($deliveryPrice);
        $deliveryPrice = $deliveryPrice->removeVat(getVat());

        return [
            'id_product' => null,
            'id_variation' => null,
            'pieces' => 1,
            'pieces_reserved' => 1,
            'piece_price' => $deliveryPrice,
            'total_price' => $deliveryPrice,
            'descr' => 'Doprava a platba',
            'tax' => getVat(),
            'note' => '{"item_type":"delivery"}',
        ];
    }

    protected function getDiscountItem($discountPrice): array
    {
        $discountPrice = toDecimal($discountPrice);
        $discountPrice = $discountPrice->removeVat(getVat());

        return [
            'id_product' => null,
            'id_variation' => null,
            'pieces' => 1,
            'pieces_reserved' => 1,
            'piece_price' => $discountPrice,
            'total_price' => $discountPrice,
            'descr' => 'Celková sleva',
            'tax' => getVat(),
            'note' => '{"item_type":"discount"}',
        ];
    }

    protected function getExternalData(\SimpleXMLElement $xml): array
    {
        return [
            (string) $xml->ID,
            [
                'cash_on_delivery' => (string) $xml->COD,
                'delivery_method_id' => (string) $xml->DELIVERY_METHOD_ID,
            ],
        ];
    }

    public function out(array $config): void
    {
        if (isDevelopment()) {
            return;
        }

        if (!($clientId = $config['api_key'] ?? null)) {
            $this->addActivityLog(
                'V nastavení chybí API klíč',
                $config
            );

            return;
        }

        $this->sendCancelledOrders($clientId);
        $this->sendShippedOrders($clientId);
    }

    protected function sendCancelledOrders($clientId)
    {
        $qb = sqlQueryBuilder()->select('o.id')->from('orders', 'o')
            ->where(Operator::isNotNull(JsonOperator::value('o.note_admin', 'mall.order_id')))
            ->andWhere('o.status_storno = 1')
            ->andWhere(Operator::isNull(JsonOperator::value('o.note_admin', 'mall.cancelledSent')))
            ->groupBy('o.id');

        foreach ($qb->execute() as $item) {
            $order = new \Order();
            $order->createFromDB($item['id']);
            $mallData = $order->getData('mall');
            $params = [
                'confirmed' => true,
                'status' => 'cancelled',
            ];

            if ($this->sendOrderUpdate($clientId, $mallData['order_id'], $params)) {
                $mallData['cancelledSent'] = true;

                $order->setData('mall', $mallData);
                $order->logHistory('Objednávka v MALL byla aktualizována na status "cancelled"');
            }
        }
    }

    protected function sendShippedOrders($clientId)
    {
        $qb = sqlQueryBuilder()->select('o.id')->from('orders', 'o')
            ->where(Operator::isNotNull(JsonOperator::value('o.note_admin', 'mall.order_id')))
            ->andWhere('o.status_storno = 0 AND o.package_id IS NOT NULL')
            ->andWhere(Operator::inIntArray($this->getShippedStatuses(), 'o.status'))
            ->andWhere(Operator::isNull(JsonOperator::value('o.note_admin', 'mall.shippedSent')))
            ->groupBy('o.id');

        foreach ($qb->execute() as $item) {
            $order = new \Order();
            $order->createFromDB($item['id']);
            $mallData = $order->getData('mall');
            $packages = $this->orderInfo->getPackages($order);
            $package = $packages[$order->package_id] ?? end($packages);
            if (!$package) {
                continue;
            }
            $params = [
                'confirmed' => true,
                'status' => 'shipped',
                'tracking_number' => $package['package_id'],
                'tracking_url' => $package['track_url'] ?? '',
            ];

            if ($this->sendOrderUpdate($clientId, $mallData['order_id'], $params)) {
                $mallData['shippedSent'] = true;

                $order->setData('mall', $mallData);
                $order->logHistory('Objednávka v MALL byla aktualizována na status "shipped"');
            }
        }
    }

    protected function getShippedStatuses(): ?array
    {
        return getStatuses('handled');
    }

    protected function sendOrderUpdate(string $clientId, $mall_order_id, array $params): bool
    {
        $ch = curl_init();

        $url = self::URL_API.$mall_order_id.'?client_id='.$clientId;
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type:application/json']);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
        $result = curl_exec($ch);
        curl_close($ch);

        $result = json_decode($result, true);

        if ('OK' != $result['result']['status'] ?? null) {
            $message = $result['result']['message'] ?? '';
            $data = [
                'mall_order_id' => $mall_order_id,
                'params' => $params,
                'result' => $result['result'] ?? null,
            ];

            addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_SYNC,
                sprintf('Dropshipment MALL: chyba při odeslání aktualizace stavů objednávky "%s"', $message), $data);

            if (str_starts_with($message, 'Final status') && str_ends_with($message, 'cannot be changed')) {
                return true;
            }

            return false;
        }

        return true;
    }

    protected function isDropshipOrderValidToImport(\SimpleXMLElement $xml): bool
    {
        return parent::isDropshipOrderValidToImport($xml) && $this->isValidToImport($xml);
    }

    protected function isValidToImport(\SimpleXMLElement $order): bool
    {
        $found = sqlQueryBuilder()
            ->select('id')
            ->from('orders')
            ->where(
                Operator::equals(
                    [
                        JsonOperator::value('note_admin', 'mall.order_id') => (string) $order->ID,
                    ]
                )
            )->execute()->fetchColumn();

        // objednavka uz je vytvorena
        if ($found) {
            return false;
        }

        return true;
    }

    protected function createOrder(\SimpleXMLElement $order, array $data): \Order
    {
        return $this->createDropshipOrder($order, $data);
    }

    protected function getDeliveryType(\SimpleXMLElement $orderItem): ?\DeliveryType
    {
        return $this->getDeliveryTypeByConfiguration($orderItem);
    }

    private function prepareItems(\SimpleXMLElement $items): array
    {
        $result = [];

        foreach ($items as $item) {
            $itemCode = (string) $item->ID;
            $parsed = explode('_', $itemCode);

            $productID = $parsed[0] ?? $itemCode;
            $variationID = $parsed[1] ?? null;

            // check that productID exists
            if (!$this->selectSQL('products', ['id' => $productID], ['id'])->fetch()) {
                $productID = null;
            }

            // check that variationID exists
            if ($variationID && !$this->selectSQL('products_variations', ['id' => $variationID], ['id'])->fetch()) {
                $variationID = null;
            }

            // create name of item
            $descr = 'Položka kód: '.$itemCode;
            if ($productID) {
                $title = $this->selectSQL('products', ['id' => $productID], ['title'])->fetchColumn();
                if (!empty($title)) {
                    $descr = $title;
                }
                if ($variationID) {
                    $title = $this->selectSQL('products_variations', ['id' => $variationID], ['title'])->fetchColumn();
                    if (!empty($title)) {
                        $descr .= ' ('.$title.')';
                    }
                }
            }

            $price = toDecimal((string) $item->PRICE);
            $vat = (string) $item->VAT;
            $pieces = toDecimal((string) $item->QUANTITY);

            $price = $price->removeVat($vat);

            $itemTotalPrice = $price->mul($pieces);

            $result[] = $this->modifyItem([
                'id_product' => $productID,
                'id_variation' => $variationID,
                'pieces' => $pieces,
                'pieces_reserved' => (string) $item->QUANTITY,
                'piece_price' => $price,
                'total_price' => $itemTotalPrice,
                'tax' => (string) $item->VAT,
                'descr' => $descr,
                'note' => json_encode(['item_type' => OrderItemInfo::TYPE_PRODUCT]),
            ], $item);
        }

        return $result;
    }

    protected function getDeliveryTypeByConfiguration(\SimpleXMLElement $order): ?\DeliveryType
    {
        $deliveryMethod = (string) $order->DELIVERY_METHOD;
        $country = (string) $order->ADDRESS->COUNTRY;
        $cod = (string) $order->COD;

        $config = $this->getConfiguration();

        $deliveryId = null;
        foreach ($config['deliveries'] ?? [] as $item) {
            if ($deliveryMethod == $item['id_external'] && (empty($item['country']) || $item['country'] == $country)) {
                $deliveryId = (int) $item['id_delivery'];
                break;
            }
        }

        if ($cod > 0) {
            $paymentId = empty($config['payments']['cod']) ? null : (int) $config['payments']['cod'];
        } else {
            $paymentId = empty($config['payments']['paid']) ? null : (int) $config['payments']['paid'];
        }

        return $this->findDeliveryType($deliveryId, $paymentId);
    }

    public function prepareConfigurationData(array $data): array
    {
        foreach ($data['deliveries'] ?? [] as $key => $item) {
            $item = array_filter($item);

            if (!empty($item['delete'])) {
                unset($data['deliveries'][$key]);
                continue;
            }

            if ($key <= 0) {
                if (!empty($item['id_external']) && !empty($item['id_delivery'])) {
                    $data['deliveries'][] = $item;
                }

                unset($data['deliveries'][$key]);
            }
        }

        return $data;
    }
}
