<?php

namespace Myvetshop\Module\Clinique\Import\Sync\Syncer;

use Myvetshop\Module\Clinique\Import\Model\OrderDetailModel;
use Myvetshop\Module\Clinique\Import\Model\OrderDetailTaxModel;
use Myvetshop\Module\Clinique\Import\Model\OrderModel;
use Myvetshop\Module\Clinique\Import\Sync\SyncStatistics;

class OrdersSyncer
{
    protected \Db $db;

    public function __construct(\Db $db)
    {
        $this->db = $db;
    }

    /**
     * @param \Customer $customer
     * @param list<OrderModel> $orders
     *
     * @return array<string, \Order>
     */
    public function getOrders(\Customer $customer, array $orders): array
    {
        $ret = [];

        if (!empty($orders)) {
            $orderReferences = \array_map(
                fn (OrderModel $orderModel) => $orderModel->reference,
                $orders
            );

            $orderRaw = $this->db->executeS(
                'SELECT o.*'
                . ' FROM ' . _DB_PREFIX_ . 'orders o'
                . ' WHERE o.id_customer = ' . (int) $customer->id
                . ' AND o.current_state = 15'
                . ' AND o.reference in ("' . \implode('","', $orderReferences) . '")',
                true,
                false
            );

            if (!\is_array($orderRaw)) {
                $orderRaw = [];
            }

            $ret = \ObjectModel::hydrateCollection(
                \Order::class,
                $orderRaw
            );
        }

        return \array_reduce(
            $ret,
            function (array $carry, \Order $order): array {
                $carry[$order->reference] = $order;

                return $carry;
            },
            []
        );
    }

    /**
     * @internal
     */
    public function fillOrder(
        \Order $order,
        \Customer $customer,
        \Clinique $clinique,
        \Cart $cart,
        OrderModel $orderModel,
        \Address $addressInvoice,
        \Address $addressDelivery
    ): \Order {
        $order->id_address_invoice = (int) $addressInvoice->id;
        $order->id_address_delivery = (int) $addressDelivery->id;
        $order->id_shop_group = 1;
        $order->id_shop = 1;
        $order->id_cart = (int) $cart->id;
        $order->id_currency = 1;
        $order->id_lang = 1;
        $order->id_customer = $customer->id;
        $order->id_carrier = $clinique->id_carrier;
        $order->current_state = 15;
        $order->secure_key = $orderModel->secureKey;
        $order->payment = $orderModel->payment;
        $order->module = $orderModel->module;
        $order->conversion_rate = 1.0;
        $order->total_discounts = (float) $orderModel->totalDiscounts;
        $order->total_discounts_tax_incl = (float) $orderModel->totalDiscountsTaxIncl;
        $order->total_discounts_tax_excl = (float) $orderModel->totalDiscountsTaxExcl;
        $order->total_paid = (float) $orderModel->totalPaid;
        $order->total_paid_tax_incl = (float) $orderModel->totalPaidTaxIncl;
        $order->total_paid_tax_excl = (float) $orderModel->totalPaidTaxExcl;
        $order->total_paid_real = (float) $orderModel->totalPaidReal;
        $order->total_products = (float) $orderModel->totalProducts;
        $order->total_products_wt = (float) $orderModel->totalProductsWt;
        $order->total_shipping = (float) $orderModel->totalShipping;
        $order->total_shipping_tax_incl = (float) $orderModel->totalShippingTaxIncl;
        $order->total_shipping_tax_excl = (float) $orderModel->totalShippingTaxExcl;
        $order->carrier_tax_rate = (float) $orderModel->carrierTaxRate;
        $order->total_wrapping = (float) $orderModel->totalWrapping;
        $order->total_wrapping_tax_incl = (float) $orderModel->totalWrappingTaxIncl;
        $order->total_wrapping_tax_excl = (float) $orderModel->totalWrappingTaxExcl;
        $order->invoice_number = 0;
        $order->invoice_date = '0000-00-00';
        $order->delivery_number = \intval($orderModel->deliveryNumber);
        $order->valid = $orderModel->valid;
        $order->reference = $orderModel->reference;
        $order->round_mode = $orderModel->roundMode;
        $order->round_type = $orderModel->roundType;
        $order->date_add = $orderModel->dateAdd->format('Y-m-d H:i:s');
        $order->date_upd = $orderModel->dateUpd->format('Y-m-d H:i:s');

        return $order;
    }

    protected function createOrUpdateOrderDetail(
        \Order $order,
        OrderDetailModel $orderDetailModel,
        OrderDetailTaxModel $orderDetailTaxModel,
        ?\OrderDetail $orderDetailInDb = null
    ): \OrderDetail {
        if (!$orderDetailInDb) {
            $orderDetail = new \OrderDetail();

            /** @var array{id_product: int, id_product_attribute: int}|null $productSupplier */
            $productSupplier = $this->db->getRow(
                'SELECT id_product, id_product_attribute'
                . ' FROM ' . _DB_PREFIX_ . 'product_supplier'
                . ' WHERE product_supplier_reference = "' . $this->db->escape($orderDetailModel->productSupplierReference) . '"'
                . ' ORDER BY id_product_attribute DESC'
            );

            $orderDetail->id_order = (int) $order->id;
            $orderDetail->id_order_invoice = 0;
            $orderDetail->product_id = \is_array($productSupplier)
                ? $productSupplier['id_product'] : $orderDetailModel->productId;
            $orderDetail->product_attribute_id = \is_array($productSupplier)
                ? $productSupplier['id_product_attribute'] : $orderDetailModel->productAttributeId;
            $orderDetail->id_shop = 1;
            $orderDetail->id_customization = 0;
            $orderDetail->product_name = $orderDetailModel->productName;
            $orderDetail->product_quantity = $orderDetailModel->productQuantity;
            $orderDetail->product_quantity_in_stock = $orderDetailModel->productQuantityInStock;
            $orderDetail->product_quantity_return = $orderDetailModel->productQuantityReturn;
            $orderDetail->product_quantity_refunded = $orderDetailModel->productQuantityRefunded;
            $orderDetail->product_quantity_reinjected = $orderDetailModel->productQuantityReinjected;
            $orderDetail->product_price = (float) $orderDetailModel->productPrice;
            $orderDetail->original_product_price = (float) $orderDetailModel->originalProductPrice;
            $orderDetail->unit_price_tax_incl = (float) $orderDetailModel->unitPriceTaxIncl;
            $orderDetail->unit_price_tax_excl = (float) $orderDetailModel->unitPriceTaxExcl;
            $orderDetail->total_price_tax_incl = (float) $orderDetailModel->totalPriceTaxIncl;
            $orderDetail->total_price_tax_excl = (float) $orderDetailModel->totalPriceTaxExcl;
            $orderDetail->reduction_percent = (float) $orderDetailModel->reductionPercent;
            $orderDetail->product_quantity_discount = $orderDetailModel->productQuantityDiscount;
            $orderDetail->product_ean13 = $orderDetailModel->productEan13;
            $orderDetail->product_isbn = '';
            $orderDetail->product_upc = $orderDetailModel->productUpc;
            $orderDetail->product_mpn = '';
            $orderDetail->product_reference = $orderDetailModel->productReference;
            $orderDetail->product_supplier_reference = $orderDetailModel->productSupplierReference;
            $orderDetail->product_weight = (float) $orderDetailModel->productWeight;
            $orderDetail->ecotax = (float) $orderDetailModel->ecotax;
            $orderDetail->ecotax_tax_rate = (float) $orderDetailModel->ecotaxTaxRate;
            $orderDetail->discount_quantity_applied = $orderDetailModel->discountQuantityApplied;
            $orderDetail->download_hash = \strval($orderDetailModel->downloadHash);
            $orderDetail->download_nb = 0;
            // @phpstan-ignore-next-line
            $orderDetail->download_deadline = '0000-00-00 00:00:00';
            $orderDetail->tax_name = $orderDetailModel->taxName;
            $orderDetail->id_warehouse = 0;
            $orderDetail->total_shipping_price_tax_excl = (float) $orderDetailModel->totalShippingPriceTaxExcl;
            $orderDetail->total_shipping_price_tax_incl = (float) $orderDetailModel->totalShippingPriceTaxIncl;
            $orderDetail->purchase_supplier_price = (float) $orderDetailModel->purchaseSupplierPrice;
            $orderDetail->original_wholesale_price = (float) $orderDetailModel->originalWholesalePrice;
            $orderDetail->total_refunded_tax_excl = (float) $orderDetailModel->totalRefundedTaxExcl;
            $orderDetail->total_refunded_tax_incl = (float) $orderDetailModel->totalRefundedTaxIncl;

            $orderDetail->save();
        } else {
            $orderDetail = $orderDetailInDb;
        }

        $row = $this->db->getRow(
            'SELECT *'
            . ' FROM ' . _DB_PREFIX_ . 'order_detail_tax'
            . ' WHERE id_order_detail = ' . (int) $orderDetail->id
        );

        if (!$row) {
            $this->db->execute(
                'INSERT INTO ' . _DB_PREFIX_ . 'order_detail_tax' .
                ' (id_order_detail, id_tax, unit_amount, total_amount)'
                . ' VALUES ('
                . (int) $orderDetail->id
                . ', 1'
                . ', "' . $this->db->escape(\strval($orderDetailTaxModel->unitAmount)) . '"'
                . ', "' . $this->db->escape(\strval($orderDetailTaxModel->totalAmount)) . '")'
            );
        }

        return $orderDetail;
    }

    /**
     * @param list<OrderDetailModel> $orderDetails
     * @param array<int, \Address> $addressesMap
     *
     * @return \Cart
     */
    protected function createOrUpdateCart(
        \Customer $customer,
        \Clinique $clinique,
        OrderModel $order,
        array $orderDetails,
        array $addressesMap
    ): \Cart {
        $cart = new \Cart();

        $cart->id_shop_group = $order->idShopGroup;
        $cart->id_address_invoice = (int) $addressesMap[$order->idAddressInvoice]->id;
        $cart->id_address_delivery = (int) $addressesMap[$order->idAddressDelivery]->id;
        $cart->id_currency = $order->idCurrency;
        $cart->id_customer = $customer->id;
        $cart->id_guest = 0;
        $cart->id_lang = 1;
        $cart->mobile_theme = false;
        $cart->secure_key = $order->secureKey;
        $cart->id_carrier = $clinique->id_carrier;
        $cart->date_add = $order->dateAdd->format('Y-m-d H:i:s');
        $cart->date_upd = $order->dateUpd->format('Y-m-d H:i:s');

        $cart->save(false, false);

        $query = 'INSERT IGNORE INTO ' . _DB_PREFIX_ . 'cart_product ('
            . 'id_cart, id_product, id_address_delivery, id_shop'
            . ', id_product_attribute, id_customization, quantity, date_add'
            . ') VALUES ';

        $values = [];
        foreach ($orderDetails as $orderDetail) {
            /** @var array{id_product: int, id_product_attribute: int}|null $productSupplier */
            $productSupplier = $this->db->getRow(
                'SELECT id_product, id_product_attribute'
                . ' FROM ' . _DB_PREFIX_ . 'product_supplier'
                . ' WHERE product_supplier_reference = "' . $this->db->escape($orderDetail->productSupplierReference) . '"'
                . ' ORDER BY id_product_attribute DESC'
            );

            $values[] = '('
                . (int) $cart->id
                . ', ' . (int) (\is_array($productSupplier) ? $productSupplier['id_product'] : $orderDetail->productId)
                . ', ' . (int) $cart->id_address_delivery
                . ', 1'
                . ', ' . (int) (\is_array($productSupplier) ? $productSupplier['id_product_attribute'] : $orderDetail->productAttributeId)
                . ', 0'
                . ', ' . (int) $orderDetail->productQuantity
                . ', "' . $this->db->escape($cart->date_add) . '"'
                . ')';
        }

        if (!empty($values)) {
            $this->db->execute($query . \implode(',', $values));
        }

        return $cart;
    }

    /**
     * @param OrderModel $orderModel
     * @param list<OrderDetailModel> $orderDetailModels
     * @param list<OrderDetailTaxModel> $orderDetailTaxesModels
     * @param array<int, \Address> $addressesMap
     * @param \Order|null $orderInDb
     *
     * @return \Order
     */
    public function createOrUpdate(
        SyncStatistics $syncStatistics,
        \Customer $customer,
        \Clinique $clinique,
        OrderModel $orderModel,
        array $orderDetailModels,
        array $orderDetailTaxesModels,
        array $addressesMap,
        ?\Order $orderInDb
    ): \Order {
        if (!$orderInDb) {
            ++$syncStatistics->nbOrdersCreated;

            if (!isset($addressesMap[$orderModel->idAddressInvoice])) {
                throw new \Exception('Unknown address : ' . $orderModel->idAddressInvoice);
            }
            if (!isset($addressesMap[$orderModel->idAddressDelivery])) {
                throw new \Exception('Unknown address : ' . $orderModel->idAddressDelivery);
            }

            // Create CART
            $cart = $this->createOrUpdateCart($customer, $clinique, $orderModel, $orderDetailModels, $addressesMap);

            $order = $this->fillOrder(
                new \Order(),
                $customer,
                $clinique,
                $cart,
                $orderModel,
                $addressesMap[$orderModel->idAddressInvoice],
                $addressesMap[$orderModel->idAddressDelivery]
            );

            $order->save(false, false);
        } else {
            ++$syncStatistics->nbOrdersUpdated;
            $order = $orderInDb;

            if ($order->date_add !== $orderModel->dateAdd->format('Y-m-d H:i:s')) {
                $order->date_add = $orderModel->dateAdd->format('Y-m-d H:i:s');
                $order->date_upd = $orderModel->dateUpd->format('Y-m-d H:i:s');

                $order->save(false, false);
            }
        }

        // Sync OrderDetails

        /** @var \OrderDetail[] $orderDetails */
        $orderDetails = \ObjectModel::hydrateCollection(
            \OrderDetail::class,
            \OrderDetail::getList((int) $order->id)
        );

        foreach ($orderDetailModels as $i => $orderDetailModel) {
            $orderDetails[$i] = $this->createOrUpdateOrderDetail(
                $order,
                $orderDetailModel,
                $orderDetailTaxesModels[$orderDetailModel->idOrderDetail],
                $orderDetails[$i] ?? null
            );
        }

        // Create/Update payment
        $orderPayments = $order->getOrderPayments();
        if (empty($orderPayments)) {
            $orderPayment = new \OrderPayment();
            $orderPayment->order_reference = $order->reference;
            $orderPayment->id_currency = $order->id_currency;
            $orderPayment->amount = $order->total_paid_tax_incl;
            $orderPayment->payment_method = 'Paiement CB';
            $orderPayment->conversion_rate = 1.0;

            $orderPayment->save();
        }

        // Create history if needed
        $history = $order->getHistory(1);
        if (!\is_array($history)) {
            $history = [];
        }
        if (empty($history)) {
            $order->current_state = 0;
            $order->setCurrentState(15, 5);
        }

        return $order;
    }

    /**
     * @param SyncStatistics $syncStatistics
     * @param \Customer $customer
     * @param array<int, \Address> $addressesMap
     * @param list<OrderModel> $orders
     * @param list<OrderDetailModel> $orderDetails
     * @param list<OrderDetailTaxModel> $orderDetailTaxes
     *
     * @return array<string, \Order>
     */
    public function sync(
        SyncStatistics $syncStatistics,
        \Customer $customer,
        \Clinique $clinique,
        array $addressesMap,
        array $orders,
        array $orderDetails,
        array $orderDetailTaxes
    ): array {
        $existingOrders = $this->getOrders($customer, $orders);

        foreach ($orders as $orderModel) {
            $orderDetailsForOrder = \array_values(
                \array_filter(
                    $orderDetails,
                    fn (OrderDetailModel $detailModel) => (int) $detailModel->idOrder === (int) $orderModel->idOrder
                )
            );

            $orderDetailsIds = \array_reduce(
                $orderDetailsForOrder,
                function (array $ids, OrderDetailModel $orderDetailModel) {
                    $ids[] = $orderDetailModel->idOrderDetail;

                    return $ids;
                },
                []
            );

            $orderDetailTaxesForOrder = \array_reduce(
                \array_filter(
                    $orderDetailTaxes,
                    fn (OrderDetailTaxModel $detailTaxModel) => \in_array($detailTaxModel->idOrderDetail, $orderDetailsIds)
                ),
                function (array $carry, OrderDetailTaxModel $detailTaxModel): array {
                    $carry[$detailTaxModel->idOrderDetail] = $detailTaxModel;

                    return $carry;
                },
                []
            );

            $existingOrders[$orderModel->reference] = $this->createOrUpdate(
                $syncStatistics,
                $customer,
                $clinique,
                $orderModel,
                $orderDetailsForOrder,
                $orderDetailTaxesForOrder,
                $addressesMap,
                $existingOrders[$orderModel->reference] ?? null,
            );
        }

        return $existingOrders;
    }
}
