<?php
/**
 * For the full copyright and license information, please view the
 * docs/licenses/LICENSE.txt file that was distributed with this source code.
 */

namespace PrestaShop\PrestaShop\Adapter\Order\QueryHandler;

use Currency;
use Db;
use Image;
use ImageManager;
use Order;
use OrderInvoice;
use OrderReturn;
use OrderSlip;
use Pack;
use PrestaShop\Decimal\DecimalNumber;
use PrestaShop\PrestaShop\Adapter\Order\AbstractOrderHandler;
use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsQueryHandler;
use PrestaShop\PrestaShop\Core\Domain\Order\Query\GetOrderProductsForViewing;
use PrestaShop\PrestaShop\Core\Domain\Order\QueryHandler\GetOrderProductsForViewingHandlerInterface;
use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderProductCustomizationForViewing;
use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderProductCustomizationsForViewing;
use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderProductForViewing;
use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderProductsForViewing;
use PrestaShop\PrestaShop\Core\Image\Parser\ImageTagSourceParserInterface;
use PrestaShop\PrestaShop\Core\Localization\CLDR\ComputingPrecision;
use PrestaShop\PrestaShop\Core\Localization\Locale;
use PrestaShop\PrestaShop\Core\Util\Sorter;
use PrestaShopBundle\Entity\Repository\ShipmentRepository;
use Product;
use Shop;
use StockAvailable;

/**
 * Handles GetOrderProductsForViewing query using legacy object models
 */
#[AsQueryHandler]
final class GetOrderProductsForViewingHandler extends AbstractOrderHandler implements GetOrderProductsForViewingHandlerInterface
{
    public function __construct(
        private readonly ImageTagSourceParserInterface $imageTagSourceParser,
        private readonly int $contextLanguageId,
        private readonly Locale $locale,
        private readonly ShipmentRepository $shipmentRepository
    ) {
    }

    public function handle(GetOrderProductsForViewing $query): OrderProductsForViewing
    {
        $order = $this->getOrder($query->getOrderId());
        $taxCalculationMethod = $this->getOrderTaxCalculationMethod($order);

        $products = $order->getProducts();
        $currency = new Currency((int) $order->id_currency);
        $computingPrecision = new ComputingPrecision();
        $precision = $computingPrecision->getPrecision($currency->precision);

        foreach ($products as &$product) {
            if ($product['image'] instanceof Image) {
                $name = 'product_mini_' . (int) $product['product_id'] . (isset($product['product_attribute_id']) ? '_' . (int) $product['product_attribute_id'] : '') . '.jpg';
                // generate image cache, only for back office
                $product['image_tag'] = ImageManager::thumbnail(_PS_IMG_DIR_ . 'p/' . $product['image']->getExistingImgPath() . '.jpg', $name, 45, 'jpg');
                if (file_exists(_PS_TMP_IMG_DIR_ . $name)) {
                    $product['image_size'] = getimagesize(_PS_TMP_IMG_DIR_ . $name);
                } else {
                    $product['image_size'] = false;
                }
            }

            // Get total customized quantity for current product
            $customized_product_quantity = 0;

            $customizations = [];
            if (is_array($product['customizedDatas'])) {
                foreach ($product['customizedDatas'] as $customizationPerAddress) {
                    foreach ($customizationPerAddress as $customization) {
                        $customized_product_quantity += (int) $customization['quantity'];
                        foreach ($customization['datas'] as $datas) {
                            foreach ($datas as $data) {
                                $customizations[] = new OrderProductCustomizationForViewing(
                                    (int) $data['type'],
                                    (string) $data['name'],
                                    $data['value']
                                );
                            }
                        }
                    }
                }
            }

            $product['customizations'] = !empty($customizations) ? new OrderProductCustomizationsForViewing($customizations) : null;
            $product['customized_product_quantity'] = $customized_product_quantity;
            $product['current_stock'] = StockAvailable::getQuantityAvailableByProduct($product['product_id'], $product['product_attribute_id'], $product['id_shop']);
            $product['quantity_refundable'] = $product['product_quantity'] - $product['product_quantity_return'] - $product['product_quantity_refunded'];
            $product['amount_refundable'] = $product['total_price_tax_excl'] - $product['total_refunded_tax_excl'];
            $product['amount_refundable_tax_incl'] = $product['total_price_tax_incl'] - $product['total_refunded_tax_incl'];
            $product['displayed_max_refundable'] = $taxCalculationMethod === PS_TAX_EXC ? $product['amount_refundable'] : $product['amount_refundable_tax_incl'];
            $resumeAmountKey = $taxCalculationMethod === PS_TAX_EXC ? 'total_refunded_tax_excl' : 'total_refunded_tax_incl';
            $product['amount_refunded'] = $product[$resumeAmountKey] ?? 0;
            $product['refund_history'] = OrderSlip::getProductSlipDetail($product['id_order_detail']);
            $product['return_history'] = OrderReturn::getProductReturnDetail($product['id_order_detail']);

            $pack_items = $product['cache_is_pack'] ? Pack::getItemTable($product['id_product'], $this->contextLanguageId, true) : [];
            foreach ($pack_items as &$pack_item) {
                $pack_item['current_stock'] = StockAvailable::getQuantityAvailableByProduct($pack_item['id_product'], $pack_item['id_product_attribute'], $pack_item['id_shop']);
                $this->setProductImageInformation($pack_item);
                if ($pack_item['image'] instanceof Image) {
                    $name = 'product_mini_' . (int) $pack_item['id_product'] . (isset($pack_item['id_product_attribute']) ? '_' . (int) $pack_item['id_product_attribute'] : '') . '.jpg';
                    // generate image cache, only for back office
                    $pack_item['image_tag'] = ImageManager::thumbnail(_PS_IMG_DIR_ . 'p/' . $pack_item['image']->getExistingImgPath() . '.jpg', $name, 45, 'jpg');
                    if (file_exists(_PS_TMP_IMG_DIR_ . $name)) {
                        $pack_item['image_size'] = getimagesize(_PS_TMP_IMG_DIR_ . $name);
                    } else {
                        $pack_item['image_size'] = false;
                    }
                }
            }

            unset($pack_item);

            $product['pack_items'] = $pack_items;
        }

        unset($product);

        // Sort products by Reference ID (and if equals (like combination) by Supplier Reference)
        $sorter = new Sorter();
        $products = $sorter->natural(
            $products,
            $query->getProductsSorting()->getValue(),
            'product_reference',
            'product_supplier_reference'
        );

        $productsForViewing = [];

        $isOrderTaxExcluded = ($taxCalculationMethod == PS_TAX_EXC);
        foreach ($products as $product) {
            $unitPrice = $isOrderTaxExcluded ?
                $product['unit_price_tax_excl'] :
                $product['unit_price_tax_incl']
            ;

            // if rounding type is set to "per item" we must round the unit price now, otherwise values won't match
            // the totals in the order summary
            if ((int) $order->round_type === Order::ROUND_ITEM) {
                $unitPrice = (new DecimalNumber((string) $unitPrice))->round($precision, $this->getNumberRoundMode());
            }

            $totalPrice = $unitPrice * $product['product_quantity'];

            $unitPriceFormatted = $this->locale->formatPrice($unitPrice, $currency->iso_code);
            $totalPriceFormatted = $this->locale->formatPrice($totalPrice, $currency->iso_code);

            $imagePath = isset($product['image_tag']) ?
                $this->imageTagSourceParser->parse($product['image_tag']) :
                null;
            $product['product_quantity_refunded'] = $product['product_quantity_refunded'] ?: false;

            $productType = !empty($product['pack_items']) ? OrderProductForViewing::TYPE_PACK :
                OrderProductForViewing::TYPE_PRODUCT_WITHOUT_COMBINATIONS;

            $orderInvoice = new OrderInvoice($product['id_order_invoice']);
            $shipments = $this->shipmentRepository->findByOrderId($order->id);
            $shipmentIds = [];

            if ($shipments) {
                foreach ($shipments as $shipment) {
                    $shipmentProducts = $shipment->getProducts();
                    foreach ($shipmentProducts as $shipmentProduct) {
                        if ($shipmentProduct->getOrderDetailId() == $product['id_order_detail']) {
                            $shipmentIds[] = $shipment->getId();
                        }
                    }
                }
            }

            $packItems = [];
            foreach ($product['pack_items'] as $pack_item) {
                $packItemType = !empty($pack_item['pack_items']) ? OrderProductForViewing::TYPE_PACK :
                    OrderProductForViewing::TYPE_PRODUCT_WITHOUT_COMBINATIONS;
                $packItemImagePath = isset($pack_item['image_tag']) ?
                    $this->imageTagSourceParser->parse($pack_item['image_tag']) :
                    null;
                $packItems[] = new OrderProductForViewing(
                    null,
                    $pack_item['id_product'],
                    0,
                    $pack_item['name'],
                    $pack_item['reference'],
                    $pack_item['supplier_reference'],
                    $pack_item['pack_quantity'],
                    '0',
                    '0',
                    $pack_item['current_stock'],
                    $packItemImagePath,
                    '0',
                    '0',
                    '0',
                    $this->locale->formatPrice(0, $currency->iso_code),
                    0,
                    $this->locale->formatPrice(0, $currency->iso_code),
                    '0',
                    $pack_item['location'],
                    null,
                    '',
                    $packItemType,
                    (bool) Product::isAvailableWhenOutOfStock(StockAvailable::outOfStock($pack_item['id_product'])),
                    [],
                    null,
                    $pack_item['mpn']
                );
            }

            $productsForViewing[] = new OrderProductForViewing(
                $product['id_order_detail'],
                $product['product_id'],
                $product['product_attribute_id'],
                $product['product_name'],
                $product['product_reference'],
                $product['product_supplier_reference'],
                $product['product_quantity'],
                $unitPriceFormatted,
                $totalPriceFormatted,
                $product['current_stock'],
                $imagePath,
                (new DecimalNumber((string) $product['unit_price_tax_excl']))->round($precision, $this->getNumberRoundMode()),
                (new DecimalNumber((string) $product['unit_price_tax_incl']))->round($precision, $this->getNumberRoundMode()),
                (string) $product['tax_rate'],
                $this->locale->formatPrice($product['amount_refunded'], $currency->iso_code),
                $product['product_quantity_refunded'] + $product['product_quantity_return'],
                $this->locale->formatPrice($product['displayed_max_refundable'], $currency->iso_code),
                (string) $product['displayed_max_refundable'],
                $product['location'],
                !empty($product['id_order_invoice']) ? $product['id_order_invoice'] : null,
                !empty($product['id_order_invoice'])
                    ? $orderInvoice->getInvoiceNumberFormatted((int) $order->getAssociatedLanguage()->getId())
                    : '',
                $productType,
                (bool) Product::isAvailableWhenOutOfStock(StockAvailable::outOfStock($product['product_id'])),
                $packItems,
                $product['customizations'],
                $product['product_mpn'],
                $shipmentIds
            );
        }

        $offset = $query->getOffset();
        $limit = $query->getLimit();

        // @todo: its not really paginated, as all products are retrieved from legacy Order::getProducts(). But could be improved in future.
        if (null !== $offset && $limit) {
            $productsForViewing = array_slice($products, (int) $offset, (int) $limit);
        }

        return new OrderProductsForViewing($productsForViewing);
    }

    /**
     * @param array $pack_item
     */
    private function setProductImageInformation(&$pack_item): void
    {
        if (isset($pack_item['id_product_attribute']) && $pack_item['id_product_attribute']) {
            $id_image = Db::getInstance()->getValue('
                SELECT `image_shop`.id_image
                FROM `' . _DB_PREFIX_ . 'product_attribute_image` pai' .
                Shop::addSqlAssociation('image', 'pai', true) . '
                WHERE id_product_attribute = ' . (int) $pack_item['id_product_attribute']);
        }

        if (!isset($id_image) || !$id_image) {
            $id_image = Db::getInstance()->getValue('
                SELECT `image_shop`.id_image
                FROM `' . _DB_PREFIX_ . 'image` i' .
                Shop::addSqlAssociation('image', 'i', true, 'image_shop.cover=1') . '
                WHERE i.id_product = ' . (int) $pack_item['id_product']
            );
        }

        $pack_item['image'] = $id_image ? new Image((int) $id_image) : null;
        $pack_item['image_size'] = null;
    }
}
