<?php

namespace KupShop\IncomakerBundle\Util\XMLElements;

use KupShop\KupShopBundle\Config;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Query\JsonOperator;
use KupShop\KupShopBundle\Util\Contexts;
use Query\Operator;
use Query\QueryBuilder;

class OrderXMLElement implements XMLElementInterface
{
    private const TYPE_ORDER = 'order';
    private const TYPE_SALE = 'sale';

    /** @var \XMLWriter */
    protected $xmlWriter;

    /** @var QueryBuilder */
    protected $qb;

    protected $totalCount = 0;

    public function getXMLOutput($specs = null): string
    {
        $this->createQB($specs);
        $this->createRootElement();

        foreach ($this->qb->execute() as $order) {
            $this->createXmlElement($order);
        }

        $this->xmlWriter->endElement();
        $this->xmlWriter->endDocument();

        return $this->xmlWriter->outputMemory();
    }

    protected function createQB($specs = null): QueryBuilder
    {
        $this->qb = $this->createBaseQueryBuilder();

        if ($specs) {
            $this->qb->andWhere($specs);
        }

        $this->totalCount = $this->createBaseQueryBuilder()->select('COUNT(*)')->execute()->fetchOne();

        return $this->qb;
    }

    protected function createBaseQueryBuilder(): QueryBuilder
    {
        // pokud nemam prodejky, tak vratim standardni query builder
        if (!findModule(\Modules::SALES)) {
            return sqlQueryBuilder()->select('*')->from('orders', 'o');
        }

        // pokud mam zapnute prodejky, tak udelam UNION objednavek a prodejek, aby se mi do feedu dostalo oboji

        $orders = sqlQueryBuilder()
            ->select('"'.self::TYPE_ORDER.'" as type, id, order_no, id_user, id_language, currency, date_created, date_handle,
              date_updated, delivery_type, status, status_storno, total_price, total_price_without_vat, invoice_email,
              invoice_name, invoice_surname, invoice_street, invoice_custom_address, invoice_zip, invoice_city,
              invoice_country, invoice_phone')
            ->from('orders');

        $handledStatuses = getStatuses('handled');
        $handledStatus = end($handledStatuses);

        $sales = sqlQueryBuilder()
            ->select('"'.self::TYPE_SALE.'" as type, s.id, code as order_no, id_user, s.id_language, s.currency, date_created,
               date_created as date_handle, date_created as date_updated, CONCAT(dtp.name, " - ", dtd.name) as delivery_type,
               '.$handledStatus.' as status, 0 as status_storno, total_price, total_price_without_vat, u.email as invoice_email,
               u.name as invoice_name, u.surname as invoice_surname, u.street as invoice_street,  u.custom_address as invoice_custom_address,
               u.zip as invoice_zip, u.city as invoice_city, u.country as invoice_country, u.phone as invoice_phone')
            ->from('sales', 's')
            ->leftJoin('s', 'users', 'u', 'u.id = s.id_user')
            ->leftJoin('s', 'delivery_type', 'dt', 's.id_delivery_type = dt.id')
            ->leftJoin('dt', 'delivery_type_delivery', 'dtd', 'dt.id_delivery = dtd.id')
            ->leftJoin('dt', 'delivery_type_payment', 'dtp', 'dt.id_payment = dtp.id')
            ->groupBy('s.id');

        return sqlQueryBuilder()->select('*')->from(Operator::union([$orders, $sales]), 'o');
    }

    protected function createRootElement(): \XMLWriter
    {
        $this->xmlWriter = new \XMLWriter();
        $this->xmlWriter->openMemory();
        $this->xmlWriter->setIndent(true);
        $this->xmlWriter->startDocument('1.0', 'UTF-8');
        $this->xmlWriter->startElement('orders');
        $this->xmlWriter->writeAttribute('version', '2.11');
        $this->xmlWriter->writeAttribute('totalItems', $this->totalCount);

        return $this->xmlWriter;
    }

    protected function createXmlElement($order)
    {
        $this->xmlWriter->startElement('o');
        $this->xmlWriter->writeAttribute('id', $order['order_no']);
        if (!empty($order['id_user'])) {
            $this->xmlWriter->writeElement('contactId', $order['id_user']);
        } else {
            $contact = [
                'email' => $order['invoice_email'],
                'firstName' => $order['invoice_name'],
                'lastName' => $order['invoice_surname'],
                'street' => $order['invoice_street'],
                'street2' => $order['invoice_custom_address'],
                'zipCode' => mb_substr($order['invoice_zip'], 0, 15),
                'city' => $order['invoice_city'],
                'country' => $order['invoice_country'],
                'phoneNumber1' => $order['invoice_phone'],
                'language' => $order['id_language'] ?? Contexts::get(LanguageContext::class)->getDefaultId(),
            ];
            $contact = array_filter($contact);
            $this->xmlWriter->startElement('contact');
            foreach ($contact as $key => $value) {
                $this->xmlWriter->writeElement($key, $value);
            }
            $this->xmlWriter->endElement();
        }
        $this->xmlWriter->writeElement('created', $order['date_created']);
        $this->xmlWriter->writeElement('finished', $order['date_handle']);
        $this->xmlWriter->writeElement('payment', $order['delivery_type']);
        $this->xmlWriter->writeElement('state', $this->getStatus($order));
        $this->xmlWriter->writeElement('total', $order['total_price']);
        $this->xmlWriter->writeElement('totalWithoutTax', $order['total_price_without_vat']);
        $this->xmlWriter->writeElement('currency', $order['currency'] ?? Contexts::get(CurrencyContext::class)->getDefaultId());
        $this->xmlWriter->startElement('items');

        foreach ($this->getItems($order) as $item) {
            $this->xmlWriter->startElement('i');
            $this->xmlWriter->writeAttribute('id', implode('_', array_filter([$item['id_product'], $item['id_variation']])));
            $this->xmlWriter->writeElement('quantity', $item['pieces']);
            $this->xmlWriter->writeElement('price', toDecimal($item['piece_price'])->addVat($item['tax']));
            $this->xmlWriter->writeElement('priceWithoutTax', toDecimal($item['piece_price']));
            $this->xmlWriter->writeElement('currency', $order['currency'] ?? Contexts::get(CurrencyContext::class)->getDefaultId());
            $this->xmlWriter->endElement();
        }

        $this->xmlWriter->endElement(); // items
        if ($customFields = $this->getCustomFields($order)) {
            $this->xmlWriter->startElement('customFields');
            foreach ($customFields as $key => $value) {
                $this->xmlWriter->startElement('f');
                $this->xmlWriter->writeAttribute('key', !empty($value['key']) ? $value['key'] : $key);
                if (!empty($value['group'])) {
                    $this->xmlWriter->writeAttribute('group', $value['group']);
                }
                $this->xmlWriter->text($value['value']);
                $this->xmlWriter->endElement(); // f
            }
            $this->xmlWriter->endElement(); // customFields
        }
        $this->xmlWriter->endElement();
    }

    protected function getItems(array $order): iterable
    {
        if (($order['type'] ?? null) === self::TYPE_SALE) {
            $qb = sqlQueryBuilder()
                ->select('*')
                ->from('sales_items')
                ->where(Operator::equals(['id_sale' => $order['id']]))
                ->andWhere('id_product IS NOT NULL');
        } else {
            $qb = sqlQueryBuilder()->select('*')
                ->from('order_items', 'oi')
                ->where(Operator::equals(['id_order' => $order['id']]))
                ->andWhere('id_product IS NOT NULL');
        }

        yield from $qb->execute();
    }

    protected function getCustomFields(array &$order)
    {
        $customFields = [
            'order_type' => ['key' => 'order_type', 'value' => $order['type'] ?? self::TYPE_ORDER],
        ];

        $charges = sqlQueryBuilder()->select('*')->from('order_items', 'oi')
            ->where(Operator::equals(['id_order' => $order['id']]))
            ->andWhere(Operator::equals([JsonOperator::value('oi.note', 'item_type') => 'orders_charge']))
            ->execute()->fetchAll();
        foreach ($charges as $item) {
            $note = json_decode($item['note'], true);
            $charge = 'charge_'.($note['id_charge'] ?? '');
            $customFields["{$charge}_name"] = ['key' => 'charge_name', 'group' => $charge, 'value' => $item['descr']];
            $customFields["{$charge}_price"] = ['key' => 'charge_price', 'group' => $charge, 'value' => toDecimal($item['piece_price'])->addVat($item['tax'])];
        }

        return $customFields;
    }

    protected function getStatus($order)
    {
        if ($order['status_storno']) {
            return 'stornována';
        }

        return Config::get()['Order']['Status']['global'][$order['status']] ?? $order['status'];
    }
}
