<?php
/**
 * Copyright © Simplyin. All rights reserved.
 */
declare(strict_types=1);

namespace Simplyin\Payment\Service;

use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Helper\Image as ImageHelper;
use Psr\Log\LoggerInterface;

/**
 * Enriches raw order data before it is sent to the SimplyIN API.
 *
 * Extracted verbatim from ProcessPayment::enrichOrderData() to keep the
 * controller within complexity limits. The logic is ProcessPayment-specific
 * and intentionally NOT generalized (a sibling CreateOrder flow has drifted).
 */
class OrderDataEnricher
{
    /**
     * @var CheckoutSession
     */
    private $checkoutSession;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var ProductRepositoryInterface
     */
    private $productRepository;

    /**
     * @var ImageHelper
     */
    private $imageHelper;

    /**
     * @var ProductAttributesResolver
     */
    private $productAttributesResolver;

    /**
     * @var PaymentDisplayFormatter
     */
    private $paymentDisplayFormatter;

    /**
     * Constructor
     * @param CheckoutSession $checkoutSession
     * @param LoggerInterface $logger
     * @param ProductRepositoryInterface $productRepository
     * @param ImageHelper $imageHelper
     * @param ProductAttributesResolver $productAttributesResolver
     * @param PaymentDisplayFormatter $paymentDisplayFormatter
     */
    public function __construct(
        CheckoutSession $checkoutSession,
        LoggerInterface $logger,
        ProductRepositoryInterface $productRepository,
        ImageHelper $imageHelper,
        ProductAttributesResolver $productAttributesResolver,
        PaymentDisplayFormatter $paymentDisplayFormatter
    ) {
        $this->checkoutSession = $checkoutSession;
        $this->logger = $logger;
        $this->productRepository = $productRepository;
        $this->imageHelper = $imageHelper;
        $this->productAttributesResolver = $productAttributesResolver;
        $this->paymentDisplayFormatter = $paymentDisplayFormatter;
    }

    /**
     * Enrich order data with required fields for the ProcessPayment flow
     *
     * @param array $orderData
     * @param bool $isAccountCreation
     * @return array
     */
    public function enrichForProcessPayment($orderData, $isAccountCreation = false)
    {
        $orderData = $this->normalizeTotals($orderData);
        $orderData = $this->applyPaymentMethodDefaults($orderData);

        if (isset($orderData['items']) && is_array($orderData['items'])) {
            foreach ($orderData['items'] as &$item) {
                $item = $this->enrichItem($item, $orderData['currency']);
            }
            unset($item);
        }

        $orderData['placedDuringAccountCreation'] = $isAccountCreation;

        // Remove payment-specific fields (but keep paymentMethod and paymentMethodTitle)
        unset($orderData['saveCard'], $orderData['cardLast4'], $orderData['cardToken']);
        unset($orderData['cardName'], $orderData['cardProvider'], $orderData['blikCode']);
        unset($orderData['useSavedCard'], $orderData['savedCardId'], $orderData['savedCardToken']);
        unset($orderData['savedCardCvv'], $orderData['cardBrand'], $orderData['paymentMethodName']);

        $orderData = $this->normalizeAddresses($orderData);
        $orderData = $this->applyParcelLocker($orderData);

        // Clean up fields not needed by API
        unset($orderData['deliveryType'], $orderData['parcelLockerData']);

        return $orderData;
    }

    /**
     * Cast monetary totals and ensure a currency is set
     *
     * @param array $orderData
     * @return array
     */
    private function normalizeTotals($orderData)
    {
        if (isset($orderData['price'])) {
            $orderData['price'] = (float)$orderData['price'];
        }
        if (isset($orderData['shippingPrice'])) {
            $orderData['shippingPrice'] = (float)$orderData['shippingPrice'];
        }

        if (!isset($orderData['currency']) || empty($orderData['currency'])) {
            $orderData['currency'] = 'PLN';
        }

        return $this->applyQuoteAmounts($orderData);
    }

    /**
     * Add the B2C gross *_amount fields from the active checkout quote.
     *
     * Discount is already negative in Magento (not re-negated). Best-effort:
     * a missing/failed quote leaves the payload unchanged (payment-first safe).
     *
     * @param array $orderData
     * @return array
     */
    private function applyQuoteAmounts($orderData)
    {
        try {
            $quote = $this->checkoutSession->getQuote();
            if (!$quote || !$quote->getId()) {
                return $orderData;
            }

            $address = $quote->isVirtual() ? $quote->getBillingAddress() : $quote->getShippingAddress();

            $orderData['total_amount'] = (float) $quote->getGrandTotal();
            $orderData['subtotal_amount'] = (float) $address->getSubtotalInclTax();
            $orderData['discount_amount'] = (float) $address->getDiscountAmount();
            $orderData['shipping_amount'] = (float) $address->getShippingInclTax();
            $orderData['tax_amount'] = (float) $address->getTaxAmount();
        } catch (\Exception $e) {
            $this->logger->debug('SimplyIN: could not resolve quote amounts: ' . $e->getMessage());
        }

        return $orderData;
    }

    /**
     * Compose payment_method / payment_method_title for SimplyIN order display.
     *
     * SimplyIN order-display contract (SIN-1726 T4), shared with the sync path via
     * PaymentDisplayFormatter. On the live checkout path the payment provider is
     * not yet known (it is resolved server-side after the status callback), so
     * payment_method is "Simply.IN"; a later reconciliation sync enriches it to
     * "Simply.IN · {Provider}". The title is composed from the frontend-supplied
     * method type, card last4 and method name (available here, before the
     * payment-specific fields are stripped below).
     *
     * @param array $orderData
     * @return array
     */
    private function applyPaymentMethodDefaults($orderData)
    {
        $methodId = (string) ($orderData['paymentMethod'] ?? '');
        if ($methodId === '' && !empty($orderData['blikCode'])) {
            $methodId = 'blik';
        }
        $last4 = (string) ($orderData['cardLast4'] ?? '');
        $typeLabel = (string) ($orderData['paymentMethodName'] ?? '');

        $orderData['payment_method'] = $this->paymentDisplayFormatter->composeSimplyinMethod('');
        $orderData['payment_method_title'] = $this->paymentDisplayFormatter
            ->composeSimplyinTitle($methodId, $last4, $typeLabel);

        unset($orderData['paymentMethod'], $orderData['paymentMethodName']);

        return $orderData;
    }

    /**
     * Normalize a single order item and resolve its thumbnail image
     *
     * @param array $item
     * @param string $currency
     * @return array
     */
    private function enrichItem($item, $currency)
    {
        $item = $this->normalizeItemNumbers($item, $currency);

        $quoteItemId = isset($item['item_id']) ? (int)$item['item_id'] : 0;
        $item = $this->applyItemTotal($item, $quoteItemId);

        //config variant image get
        $imageUrl = isset($item['thumbnailUrl']) ? $item['thumbnailUrl'] : '';
        try {
            $targetProductId = $this->resolveTargetProductId($quoteItemId, $item);

            if ($targetProductId) {
                $product = $this->productRepository->getById($targetProductId);
                $imageUrl = $this->imageHelper->init($product, 'image')
                    ->setImageFile($product->getImage())
                    ->resize(200)
                    ->getUrl();
                $item = array_merge($item, $this->productAttributesResolver->resolve($product));
            }

            $item['thumbnailUrl'] = $imageUrl;
        } catch (\Exception $e) {
            $this->logger->debug('Could not enrich product item: ' . $e->getMessage());
        }

        return $item;
    }

    /**
     * Add the per-line gross total_amount from the quote item row total
     * (incl. tax), falling back to price x quantity when unavailable.
     *
     * @param array $item
     * @param int $quoteItemId
     * @return array
     */
    private function applyItemTotal($item, $quoteItemId)
    {
        if (isset($item['total_amount'])) {
            $item['total_amount'] = (float) $item['total_amount'];
            return $item;
        }

        $fallback = (float) $item['price'] * (int) $item['quantity'];
        try {
            $quote = $this->checkoutSession->getQuote();
            $quoteItem = ($quoteItemId && $quote) ? $quote->getItemById($quoteItemId) : null;
            $rowTotal = $quoteItem ? $quoteItem->getRowTotalInclTax() : null;
            $item['total_amount'] = $rowTotal !== null ? (float) $rowTotal : $fallback;
        } catch (\Exception $e) {
            $item['total_amount'] = $fallback;
        }

        return $item;
    }

    /**
     * Cast item monetary/quantity fields and set its currency
     *
     * @param array $item
     * @param string $currency
     * @return array
     */
    private function normalizeItemNumbers($item, $currency)
    {
        $price = isset($item['price']) ? (float)$item['price'] : 0.0;
        $item['price'] = $price;
        $item['currency'] = $currency;
        $item['price_net'] = isset($item['price_net']) ? (float)$item['price_net'] : $price;
        $item['tax_amount'] = isset($item['tax_amount']) ? (float)$item['tax_amount'] : 0.0;
        $item['tax_rate'] = isset($item['tax_rate']) ? (float)$item['tax_rate'] : 0.0;
        $item['quantity'] = isset($item['quantity']) ? (int)$item['quantity'] : 1;

        return $item;
    }

    /**
     * Resolve the product id whose image should be used for a quote item
     *
     * @param int $quoteItemId
     * @param array $item
     * @return int|null
     */
    private function resolveTargetProductId($quoteItemId, $item)
    {
        $quote = $this->checkoutSession->getQuote();
        $targetProductId = null;

        if ($quoteItemId && $quote) {
            $quoteItem = $quote->getItemById($quoteItemId);
            if ($quoteItem) {
                // For configurable/bundle/grouped: get the child simple product
                $children = $quoteItem->getChildren();
                if (!empty($children)) {
                    $targetProductId = $children[0]->getProductId();
                } else {
                    $targetProductId = $quoteItem->getProductId();
                }
            }
        }

        // Fallback to item id if quote item lookup didn't work
        if (!$targetProductId && isset($item['id'])) {
            $targetProductId = (int)$item['id'];
        }

        return $targetProductId;
    }

    /**
     * Ensure address fields and preserve _id for existing SimplyIN addresses
     *
     * @param array $orderData
     * @return array
     */
    private function normalizeAddresses($orderData)
    {
        if (isset($orderData['billingData'])) {
            $orderData['billingData'] = $this->normalizeAddress($orderData['billingData'], 'billing');
        }

        if (isset($orderData['shippingData'])) {
            $orderData['shippingData'] = $this->normalizeAddress($orderData['shippingData'], 'shipping');
        }

        return $orderData;
    }

    /**
     * Default the apartment number and log a preserved address _id
     *
     * @param array $address
     * @param string $label
     * @return array
     */
    private function normalizeAddress($address, $label)
    {
        if (!isset($address['appartmentNumber']) || $address['appartmentNumber'] == '') {
            $address['appartmentNumber'] = '';
        }
        // Preserve _id if present - this prevents duplicate address creation in SimplyIN
        if (isset($address['_id']) && !empty($address['_id'])) {
            $this->logger->info('SimplyIN ProcessPayment: Using existing ' . $label . ' address _id: ' . $address['_id']);
        }

        return $address;
    }

    /**
     * Handle parcel locker delivery - use parcelLockerMinimalInfo instead of shippingData
     *
     * This matches WooCommerce behavior where parcelLockerMinimalInfo replaces shippingData
     *
     * @param array $orderData
     * @return array
     */
    private function applyParcelLocker($orderData)
    {
        $deliveryType = $orderData['deliveryType'] ?? 'courier';
        $parcelLockerData = $orderData['parcelLockerData'] ?? null;

        if ($deliveryType === 'parcel_locker' && $parcelLockerData) {
            // Build parcelLockerMinimalInfo
            $lockerId = $parcelLockerData['lockerId'] ?? '';
            $providerName = strtolower($parcelLockerData['providerName'] ?? $parcelLockerData['provider'] ?? '');

            if (!empty($lockerId) && !empty($providerName)) {
                $orderData['parcelLockerMinimalInfo'] = [
                    'lockerId' => (string) $lockerId,
                    'providerName' => (string) $providerName
                ];

                // Remove shippingData when using parcelLockerMinimalInfo (matches WooCommerce behavior)
                unset($orderData['shippingData']);

                $this->logger->info('SimplyIN ProcessPayment: Using parcelLockerMinimalInfo, removed shippingData', [
                    'lockerId' => $lockerId,
                    'providerName' => $providerName
                ]);
            }
        }

        return $orderData;
    }
}
