<?php

declare(strict_types=1);

namespace KupShop\OrderingBundle\Feed\Wrapper;

use KupShop\FeedsBundle\Wrapper\BaseWrapper;
use KupShop\FeedsBundle\Wrapper\CurrencyWrapper;
use KupShop\KupShopBundle\Config;
use KupShop\KupShopBundle\Context\ContextManager;
use KupShop\KupShopBundle\Context\CountryContext;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\OrderingBundle\OrderList\OrderCollection;
use KupShop\OrderingBundle\Util\Order\OrderInfo;
use Query\Operator;

class OrderWrapper extends BaseWrapper
{
    protected static $objectType = \Order::class;

    /** @var \Order */
    protected $object;

    /** @var OrderCollection */
    protected $orders;

    /** @var bool */
    protected $itemsDiscountsFetched = false;

    /** @var array */
    protected $feedRow;

    private $totalPriceWrapper;

    private $currencyWrapper;

    private $orderItemWrapper;

    /** @var OrderDeliveryTypeWrapper */
    protected $deliveryTypeWrapper;

    /** @var OrderPaymentTypeWrapper */
    protected $paymentTypeWrapper;

    /** @var OrderInfo */
    private $orderInfo;

    private $currencyContext;

    private $countryContext;

    protected $contextManager;

    public function __construct(
        TotalPriceWrapper $totalPriceWrapper,
        CurrencyWrapper $currencyWrapper,
        OrderItemWrapper $orderItemWrapper,
        OrderDeliveryTypeWrapper $deliveryTypeWrapper,
        OrderPaymentTypeWrapper $paymentTypeWrapper,
        OrderInfo $orderInfo,
        CurrencyContext $currencyContext,
        CountryContext $countryContext,
        ContextManager $contextManager,
    ) {
        $this->totalPriceWrapper = $totalPriceWrapper;
        $this->currencyWrapper = $currencyWrapper;
        $this->orderItemWrapper = $orderItemWrapper;
        $this->deliveryTypeWrapper = $deliveryTypeWrapper;
        $this->paymentTypeWrapper = $paymentTypeWrapper;
        $this->orderInfo = $orderInfo;
        $this->currencyContext = $currencyContext;
        $this->countryContext = $countryContext;
        $this->contextManager = $contextManager;
    }

    /**
     * @private
     */
    public function activateOrderFor(callable $callback, bool $printOutput = true)
    {
        return $this->contextManager->activateOrder($this->object, function () use ($callback) {
            echo $callback();
        });
    }

    /**
     * @private
     */
    public function setFeedRow(?array $feedRow): void
    {
        $this->feedRow = $feedRow;
    }

    /**
     * @private
     */
    public function setOrders(OrderCollection $orders): void
    {
        $this->orders = $orders;
        $this->itemsDiscountsFetched = false;
    }

    /** Interní ID objednávky */
    public function field_id(): ?int
    {
        return !empty($this->object->id) ? (int) $this->object->id : null;
    }

    /** Číslo objednávky */
    public function field_number(): ?string
    {
        return $this->object->order_no ?? null;
    }

    /** Číslo faktury */
    public function field_invoice_number(): ?string
    {
        return $this->object->getInvoiceNo();
    }

    /** Id uživatele */
    public function field_user_id(): ?int
    {
        return $this->object->id_user ?? null;
    }

    /** Číslo balíku */
    public function field_package_id(): ?string
    {
        return $this->object->package_id;
    }

    /** Jazyk */
    public function field_language(): string
    {
        return $this->object->getLanguage();
    }

    /** Měna */
    public function field_currency(): CurrencyWrapper
    {
        return $this->currencyWrapper->setObject(
            $this->currencyContext->getAll()[$this->object->getCurrency()]
        );
    }

    /** Kurz měny objednávky vůči defaultní měne e-shopu */
    public function field_currency_rate(): float
    {
        return (float) $this->object->currency_rate;
    }

    /** Datum vytvoření.
     *
     * Použití: utils.formatDate(date)
     */
    public function field_date(): \DateTimeInterface
    {
        // https://github.com/phpv8/v8js converts only PHP's \DateTime to js Date, \DateTimeImmutable would be passed as a regular object
        return \DateTime::createFromInterface($this->object->date_created);
    }

    /** Datum vyřízení (datum zdanitelného plnění).
     *
     * Datum vyřízení je automaticky vyplněn, pokud je objednávka převedena do některého ze stavů vyřízeno. Odpovídá také datumu zdanitelného plnění.
     *
     * Použití:
     * <code>date_handle ? utils.formatDate(date_handle) : ''</code>
     */
    public function field_date_handle(): ?\DateTimeInterface
    {
        // https://github.com/phpv8/v8js converts only PHP's \DateTime to js Date, \DateTimeImmutable would be passed as a regular object
        return !empty($this->object->date_handle) ? \DateTime::createFromInterface($this->object->date_handle) : null;
    }

    /** Datum doručení.
     *
     * Použití:
     * <code>date_delivered ? utils.formatDate(date_delivered) : ''</code>
     */
    public function field_date_delivered(): ?\DateTimeInterface
    {
        // https://github.com/phpv8/v8js converts only PHP's \DateTime to js Date, \DateTimeImmutable would be passed as a regular object
        return !empty($this->object->date_delivered) ? \DateTime::createFromInterface($this->object->date_delivered) : null;
    }

    /** Datum doručení avizované při nákupu.
     *
     * Použití:
     * <code>date_delivery_promise ? utils.formatDate(date_delivery_promise) : ''</code>
     */
    public function field_date_delivery_promise(): ?\DateTimeInterface
    {
        $deliveryDate = $this->object->getData('deliveryDate', false);
        $deliveryDatePromise = $deliveryDate ? \DateTime::createFromFormat(\DateTime::ATOM, $deliveryDate) : null;

        return $deliveryDatePromise ?: null;
    }

    /** Balíkobot - stav zásilky (trackstatus).
     *
     * <code>1</code> - Doručeno
     * <code>2</code> - V přepravě
     * <code>3</code> - Storno
     * <code>4</code> - Doručeno zpět odesílateli
     * <code>5</code> - Dobírka byla připsána na účet odesílatele
     * <code>-1</code> - Objednáno
     *
     * @module BALIKONOS
     */
    public function field_balikobot_package_status(): string
    {
        $trackstatus = $this->object->getData('balikobot_package_status', false);

        return $trackstatus ? ((string) $trackstatus) : '';
    }

    /** Příznaky objednávky.
     *
     * Např.:
     * <code>'D'</code> - Duplicitní
     * <code>'R'</code> - Nedořešená poznámka
     * <code>'NE'</code> - Nedoručitelný e-mail
     * <code>'V'</code> - Vratka
     * <code>'OSS'</code> - OSS
     */
    public function field_flags(): array
    {
        $possibleFlags = Config::get()['Order']['Flags'];
        $activeFlags = [];
        foreach ($this->object->getFlags() as $flagCode => $val) {
            if (empty($flagCode)) {
                continue;
            }
            $activeFlags[$flagCode] = [
                'code' => $flagCode,
                'name' => !empty($possibleFlags[$flagCode]['name']) ? $possibleFlags[$flagCode]['name'] : $flagCode,
            ];
        }

        return $activeFlags;
    }

    /** Číselný stav */
    public function field_status(): int
    {
        return (int) $this->object->status;
    }

    /** Název stavu */
    public function field_status_name(): string
    {
        return $this->orderInfo->getOrderStatus($this->object->status);
    }

    /** Stornováno */
    public function field_status_storno(): int
    {
        return (int) $this->object->status_storno;
    }

    /** Zaplaceno */
    public function field_status_paid(): int
    {
        return (int) $this->object->status_payed;
    }

    /** Celková cena objednávky */
    public function field_total_price(): TotalPriceWrapper
    {
        return (clone $this->totalPriceWrapper)->setObject(
            $this->object->getTotalPrice()
        );
    }

    /** E-mail uživatele */
    public function field_email(): string
    {
        return $this->object->invoice_email;
    }

    /** Fakturační jméno */
    public function field_invoice_name(): string
    {
        return $this->object->invoice_name;
    }

    /** Fakturační příjmení */
    public function field_invoice_surname(): string
    {
        return $this->object->invoice_surname;
    }

    /** Fakturační firma */
    public function field_invoice_firm(): string
    {
        return $this->object->invoice_firm;
    }

    /** Fakturační IČO */
    public function field_invoice_ico(): string
    {
        return $this->object->invoice_ico;
    }

    /** Fakturační DIČ */
    public function field_invoice_dic(): string
    {
        return $this->object->invoice_dic;
    }

    /** Fakturační ulice */
    public function field_invoice_street(): string
    {
        return $this->object->invoice_street;
    }

    /** Fakturační město */
    public function field_invoice_city(): string
    {
        return $this->object->invoice_city;
    }

    /** Fakturační PSČ */
    public function field_invoice_zip(): string
    {
        return $this->object->invoice_zip;
    }

    /** Fakturační stát */
    public function field_invoice_country(): string
    {
        if ($this->countryContext->getAll()[$this->object->invoice_country] ?? false) {
            return $this->countryContext->getAll()[$this->object->invoice_country]->getName();
        }

        return $this->object->invoice_country;
    }

    /** Fakturační stát - kód */
    public function field_invoice_country_code(): string
    {
        if ($this->countryContext->getAll()[$this->object->invoice_country] ?? false) {
            return $this->countryContext->getAll()[$this->object->invoice_country]->getId();
        }

        return $this->object->invoice_country;
    }

    /** Fakturační telefon */
    public function field_invoice_phone(): string
    {
        return $this->object->invoice_phone;
    }

    /** Dodací jméno */
    public function field_delivery_name(): string
    {
        return $this->object->delivery_name;
    }

    /** Dodací příjmení */
    public function field_delivery_surname(): string
    {
        return $this->object->delivery_surname;
    }

    /** Dodací firma */
    public function field_delivery_firm(): string
    {
        return $this->object->delivery_firm;
    }

    /** Dodací ulice */
    public function field_delivery_street(): string
    {
        return $this->object->delivery_street;
    }

    /** Dodací město */
    public function field_delivery_city(): string
    {
        return $this->object->delivery_city;
    }

    /** Dodací PSČ */
    public function field_delivery_zip(): string
    {
        return $this->object->delivery_zip;
    }

    /** Dodací stát */
    public function field_delivery_country(): string
    {
        if ($this->countryContext->getAll()[$this->object->delivery_country] ?? false) {
            return $this->countryContext->getAll()[$this->object->delivery_country]->getName();
        }

        return $this->object->delivery_country;
    }

    /** Dodací stát - kód */
    public function field_delivery_country_code(): string
    {
        if ($this->countryContext->getAll()[$this->object->delivery_country] ?? false) {
            return $this->countryContext->getAll()[$this->object->delivery_country]->getId();
        }

        return $this->object->delivery_country;
    }

    /** Dodací telefon */
    public function field_delivery_phone(): string
    {
        return $this->object->delivery_phone;
    }

    /** Poznámka od uživatele */
    public function field_note_user(): ?string
    {
        return $this->object->note_user ?: null;
    }

    /** Poznámka na faktuře */
    public function field_note_invoice(): ?string
    {
        return $this->object->note_invoice ?? null;
    }

    /** Položky objednávky */
    public function field_items(): array
    {
        $items = [];
        foreach ($this->object->fetchItems() as $item) {
            $item['currency_object'] = $this->currencyContext->getAll()[$this->object->getCurrency()];
            // lazy fetch discounts when item discounts are required
            $item['_getDiscounts'] = function () use ($item) {
                if (!$this->itemsDiscountsFetched) {
                    $this->orderInfo->fetchDiscountsForMultiple($this->orders);
                    $this->itemsDiscountsFetched = true;
                }
                $discounts = [];
                foreach ($this->object->itemsDiscounts ?? [] as $key => $discount) {
                    if (isset($discount['itemsPartials'][$item['id']])) {
                        $discounts[$key] = [
                            'name' => $discount['name'],
                            'price' => $discount['itemsPartials'][$item['id']]->asFloat(),
                        ];
                    }
                }

                return $discounts;
            };
            $items[] = (clone $this->orderItemWrapper)->setObject($item);
        }

        return $items;
    }

    /** Seznam slev */
    public function field_discounts(): array
    {
        if (!$this->itemsDiscountsFetched) {
            $this->orderInfo->fetchDiscountsForMultiple($this->orders);
            $this->itemsDiscountsFetched = true;
        }

        return array_map(function ($discount) {
            return (new OrderDiscountWrapper())->setObject($discount);
        }, $this->object->itemsDiscounts ?? []);
    }

    /** Uplatněné kupóny */
    public function field_coupons(): array
    {
        return $this->object->getData('discounts', false)['used_coupons'] ?? [];
    }

    /** Doprava */
    public function field_delivery(): ?OrderDeliveryTypeWrapper
    {
        if (!isset($this->object->__deliveryType)) {
            $this->object->__deliveryType = $this->object->getDeliveryType();
        }
        $delivery = $this->object->__deliveryType->getDelivery() ?? null;
        if (!isset($delivery)) {
            return null;
        }

        return $this->deliveryTypeWrapper->setObject($delivery);
    }

    /** Platba */
    public function field_payment(): ?OrderPaymentTypeWrapper
    {
        if (!isset($this->object->__deliveryType)) {
            $this->object->__deliveryType = $this->object->getDeliveryType();
        }
        $payment = $this->object->__deliveryType->getPayment() ?? null;
        if (!isset($payment)) {
            return null;
        }

        return $this->paymentTypeWrapper->setObject($payment);
    }

    /** ID původní objednávky.
     *
     * @module RETURNS
     */
    public function field_original_order_id(): ?int
    {
        return (int) $this->object->getData('replacement', false) ?: null;
    }

    /** Číslo původní objednávky.
     *
     * @module RETURNS
     */
    public function field_original_order_number(): ?string
    {
        $this->fetchOrigOrderInfo();

        return $this->object->origOrderNo ?? null;
    }

    /**
     * Číslo faktury původní objednávky.
     *
     * @module RETURNS
     */
    public function field_original_order_invoice_number(): ?string
    {
        $this->fetchOrigOrderInfo();

        return $this->object->origInvoiceNo ?? null;
    }

    /**
     * Zdroj objednávky.
     *
     * @module DROPSHIP
     */
    public function field_source(): array
    {
        if (!isset($this->orders->_sourceInfo_fetched)) {
            $qb = sqlQueryBuilder()
                ->select('id_order, id_dropshipment, id_external, data')
                ->from('order_dropshipment', 'od')
                ->where(Operator::inIntArray($this->orders->getOrderIds(), 'od.id_order'));

            foreach ($qb->execute() as $item) {
                if (isset($this->orders[$item['id_order']])) {
                    if (!isset($this->orders[$item['id_order']]->sourceInfo)) {
                        $this->orders[$item['id_order']]->sourceInfo = [
                            'id' => $item['id_external'],
                            'data' => json_decode($item['data'] ?? '{}', true),
                        ];
                    }
                }
            }

            $this->orders->_sourceInfo_fetched = true;
        }

        $result = [
            'source' => $this->object->source,
            'source_name' => OrderInfo::getOrderSources()[$this->object->source],
        ];

        if ($this->object->sourceInfo ?? false) {
            $result['external'] = $this->object->sourceInfo ?? [];
        }

        return $result;
    }

    protected function fetchOrigOrderInfo(): void
    {
        if (!isset($this->orders->_origOrderInfo_fetched)) {
            $qb = sqlQueryBuilder()
                ->select('o1.id as id_order, o2.order_no')
                ->from('orders', 'o1')->leftJoin('o1', 'orders', 'o2', 'JSON_VALUE(o1.note_admin,\'$.replacement\') = o2.id')
                ->where(Operator::inIntArray($this->orders->getOrderIds(), 'o1.id'));
            if ($invoices = findModule(\Modules::INVOICES)) {
                $qb->addSelect('o2.invoice_no');
            }

            foreach ($qb->execute() as $item) {
                if (isset($this->orders[$item['id_order']])) {
                    if (!isset($this->orders[$item['id_order']]->origOrderNo)) {
                        $this->orders[$item['id_order']]->origOrderNo = $item['order_no'];
                    }
                    if ($invoices && !isset($this->orders[$item['id_order']]->origInvoiceNo)) {
                        $this->orders[$item['id_order']]->origInvoiceNo = $item['invoice_no'];
                    }
                }
            }

            $this->orders->_origOrderInfo_fetched = true;
        }
    }
}
