<?php

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

use Myvetshop\Module\Clinique\Import\Kalivet\Model\OrderDetailModel;
use Myvetshop\Module\Clinique\Import\Kalivet\Model\OrderDetailTaxModel;
use Myvetshop\Module\Clinique\Import\Kalivet\Model\OrderModel;
use Myvetshop\Module\Clinique\Import\Kalivet\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;
            },
            []
        );
    }

    protected function createOrUpdateOrderDetail(
        \Order $order,
        OrderDetailModel $orderDetailModel,
        OrderDetailTaxModel $orderDetailTaxModel,
        ?\OrderDetail $orderDetail = null
    ): \OrderDetail {
        if (!$orderDetail) {
            $ret = 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'
            );

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

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

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

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

        return $ret;
    }

    /**
     * @param \Order $order
     * @param list<\OrderDetail> $orderDetails
     *
     * @return \Cart
     */
    protected function createOrUpdateCart(\Order $order, array $orderDetails): \Cart
    {
        $cart = new \Cart($order->id_cart ?: null);

        if (!$cart->id) {
            $cart->id_shop_group = $order->id_shop_group;
            $cart->id_address_delivery = $order->id_address_delivery;
            $cart->id_address_invoice = $order->id_address_invoice;
            $cart->id_currency = $order->id_currency;
            $cart->id_customer = $order->id_customer;
            $cart->id_guest = 0;
            $cart->id_lang = 1;
            $cart->mobile_theme = false;
            $cart->secure_key = $order->secure_key;
            $cart->id_carrier = $order->id_carrier;

            $cart->save();
        }

        $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) {
            $values[] = '('
                . (int) $cart->id
                . ', ' . (int) $orderDetail->product_id
                . ', ' . (int) $order->id_address_delivery
                . ', 1'
                . ', ' . (int) $orderDetail->product_attribute_id
                . ', 0'
                . ', ' . (int) $orderDetail->product_quantity
                . ', "' . $this->db->escape($order->date_add) . '"'
                . ')';
        }

        $this->db->execute($query . \implode(',', $values));

        return $cart;
    }

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

            $ret = new \Order();

            $ret->id_address_invoice = (int) $addressesMap[$orderModel->idAddressInvoice]->id;
            $ret->id_address_delivery = (int) $addressesMap[$orderModel->idAddressDelivery]->id;
            $ret->id_shop_group = 1;
            $ret->id_shop = 1;
            $ret->id_cart = 0;
            $ret->id_currency = 1;
            $ret->id_lang = 1;
            $ret->id_customer = $customer->id;
            $ret->id_carrier = $clinique->id_carrier;
            $ret->current_state = 15;
            $ret->secure_key = $orderModel->secureKey;
            $ret->payment = $orderModel->payment;
            $ret->module = $orderModel->module;
            $ret->conversion_rate = 1.0;
            $ret->total_discounts = (float) $orderModel->totalDiscounts;
            $ret->total_discounts_tax_incl = (float) $orderModel->totalDiscountsTaxIncl;
            $ret->total_discounts_tax_excl = (float) $orderModel->totalDiscountsTaxExcl;
            $ret->total_paid = (float) $orderModel->totalPaid;
            $ret->total_paid_tax_incl = (float) $orderModel->totalPaidTaxIncl;
            $ret->total_paid_tax_excl = (float) $orderModel->totalPaidTaxExcl;
            $ret->total_paid_real = (float) $orderModel->totalPaidReal;
            $ret->total_products = (float) $orderModel->totalProducts;
            $ret->total_products_wt = (float) $orderModel->totalProductsWt;
            $ret->total_shipping = (float) $orderModel->totalShipping;
            $ret->total_shipping_tax_incl = (float) $orderModel->totalShippingTaxIncl;
            $ret->total_shipping_tax_excl = (float) $orderModel->totalShippingTaxExcl;
            $ret->carrier_tax_rate = (float) $orderModel->carrierTaxRate;
            $ret->total_wrapping = (float) $orderModel->totalWrapping;
            $ret->total_wrapping_tax_incl = (float) $orderModel->totalWrappingTaxIncl;
            $ret->total_wrapping_tax_excl = (float) $orderModel->totalWrappingTaxExcl;
            $ret->invoice_number = 0;
            $ret->invoice_date = '0000-00-00';
            $ret->delivery_number = \intval($orderModel->deliveryNumber);
            $ret->valid = $orderModel->valid;
            $ret->reference = $orderModel->reference;
            $ret->round_mode = $orderModel->roundMode;
            $ret->round_type = $orderModel->roundType;

            $ret->save();
        } else {
            ++$syncStatistics->nbOrdersUpdated;
            $ret = $order;
        }

        // Sync OrderDetails

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

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

        // Create CART
        if (!$ret->id_cart) {
            $cart = $this->createOrUpdateCart($ret, $orderDetails);
            $ret->id_cart = (int) $cart->id;
            $ret->save();
        }

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

            $orderPayment->save();
        }

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

        return $ret;
    }

    /**
     * @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;
    }
}
