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

namespace Simplyin\Payment\Service;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Helper\Image as ImageHelper;
use Psr\Log\LoggerInterface;

/**
 * Builds the SimplyIN order payload from a Magento order
 */
class OrderPayloadBuilder
{
    /**
     * @var LoggerInterface
     */
    private $logger;

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

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

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

    /**
     * @var VisibleProductUrlResolver
     */
    private $visibleProductUrlResolver;

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

    /**
     * @var OrderDateFormatter
     */
    private $orderDateFormatter;

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

    /**
     * Build order data for SimplyIN API
     *
     * Matches the payload structure expected by the API (same as CreateOrder controller)
     *
     * @param \Magento\Sales\Model\Order $order
     * @return array
     */
    public function buildOrderData($order): array
    {
        $items = $this->buildItems($order);

        $this->logger->debug('SimplyIN: Order items built', ['count' => count($items)]);


        $shippingAddress = $order->getShippingAddress();
        $billingAddress = $order->getBillingAddress();

        // Build shippingData in the format expected by API
        $shippingData = $shippingAddress ? $this->mapAddress($shippingAddress) : null;

        // Build billingData in the format expected by API
        $billingData = $billingAddress ? $this->mapAddress($billingAddress) : null;

        // Get payment method info
        [$paymentMethod, $paymentMethodTitle] = $this->getPaymentInfo($order);

        // Build order data matching the API expected format.
        // Financial *_amount fields are B2C gross (incl. tax); discount is already
        // negative in Magento (not re-negated). Legacy price/shippingPrice are kept
        // during the transition.
        $orderData = [
            'price' => (float) $order->getGrandTotal(),
            'total_amount' => (float) $order->getGrandTotal(),
            'subtotal_amount' => (float) $order->getSubtotalInclTax(),
            'discount_amount' => (float) $order->getDiscountAmount(),
            'shipping_amount' => (float) $order->getShippingInclTax(),
            'tax_amount' => (float) $order->getTaxAmount(),
            'currency' => $order->getOrderCurrencyCode() ?: 'PLN',
            'items' => $items,
            'shopOrderNumber' => $order->getIncrementId(),
            'shopUserEmail' => $order->getCustomerEmail(),
            'shippingPrice' => (float) $order->getShippingAmount(),
            'payment_method' => $paymentMethod,
            'payment_method_title' => $paymentMethodTitle,
            'status' => $this->mapOrderStatus($order->getStatus()),
            'placedDuringAccountCreation' => false
        ];

        $this->logReconciliation($orderData);

        if ($shippingData) {
            $orderData['shippingData'] = $shippingData;
        }

        if ($billingData) {
            $orderData['billingData'] = $billingData;
        }

        $orderDate = $this->orderDateFormatter->format($order->getCreatedAt());
        if ($orderDate !== null) {
            $orderData['orderDate'] = $orderDate;
        }

        return [
            'newOrderData' => $orderData,
            'shopUserEmail' => $order->getCustomerEmail()
        ];
    }

    /**
     * Build the line items for the order payload
     *
     * @param \Magento\Sales\Model\Order $order
     * @return array
     */
    private function buildItems($order): array
    {
        $items = [];
        foreach ($order->getAllVisibleItems() as $item) {
            $items[] = $this->buildItem($item, $order);
        }

        return $items;
    }

    /**
     * Build a single line item for the order payload
     *
     * @param \Magento\Sales\Model\Order\Item $item
     * @param \Magento\Sales\Model\Order $order
     * @return array
     */
    private function buildItem($item, $order): array
    {
        $lineItem = [
            'name' => $item->getName(),
            'url' => '',
            'price' => (float) $item->getPriceInclTax(),
            'price_net' => (float) $item->getPrice(),
            'tax_amount' => (float) $item->getTaxAmount(),
            'tax_rate' => (float) $item->getTaxPercent(),
            'total_amount' => (float) $item->getRowTotalInclTax(),
            'quantity' => (int) $item->getQtyOrdered(),
            'currency' => $order->getOrderCurrencyCode() ?: 'PLN',
            'thumbnailUrl' => ''
        ];

        try {
            // Load in the order's store view so store-scoped attributes (url_key,
            // descriptions, option labels) resolve correctly in the cron context.
            $product = $this->productRepository->getById($item->getProductId(), false, (int) $order->getStoreId());
            $lineItem['url'] = $this->visibleProductUrlResolver->resolve($product, $order->getStoreId());
            $lineItem['thumbnailUrl'] = $this->imageHelper->init($product, 'image')
                ->setImageFile($product->getImage())
                ->resize(200)
                ->getUrl();
            $lineItem = array_merge(
                $lineItem,
                $this->productAttributesResolver->resolve(
                    $product,
                    $this->extractOrderItemOptions($item),
                    (int) $order->getStoreId()
                )
            );
        } catch (\Exception $e) {
            $this->logger->debug('Could not enrich SimplyIN item for SKU ' . $item->getSku() . ': ' . $e->getMessage());
        }

        return $lineItem;
    }

    /**
     * Extract configurable super-attributes ({name, value}) from an order item.
     *
     * @param \Magento\Sales\Model\Order\Item $item
     * @return array
     */
    private function extractOrderItemOptions($item): array
    {
        $options = $item->getProductOptions();
        if (!is_array($options) || empty($options['attributes_info'])) {
            return [];
        }

        $result = [];
        foreach ($options['attributes_info'] as $info) {
            if (isset($info['label'], $info['value'])) {
                $result[] = ['name' => (string) $info['label'], 'value' => (string) $info['value']];
            }
        }

        return $result;
    }

    /**
     * Log a warning when the gross amounts do not reconcile (subtotal + discount
     * + shipping == total). Informational only — never blocks the payload.
     *
     * @param array $orderData
     * @return void
     */
    private function logReconciliation(array $orderData): void
    {
        $expected = $orderData['subtotal_amount'] + $orderData['discount_amount'] + $orderData['shipping_amount'];
        if (abs($expected - $orderData['total_amount']) > 0.01) {
            $this->logger->warning('SimplyIN: order amounts do not reconcile', [
                'shopOrderNumber' => $orderData['shopOrderNumber'] ?? null,
                'subtotal_amount' => $orderData['subtotal_amount'],
                'discount_amount' => $orderData['discount_amount'],
                'shipping_amount' => $orderData['shipping_amount'],
                'total_amount' => $orderData['total_amount']
            ]);
        }
    }

    /**
     * Map a Magento address to the SimplyIN address format
     *
     * @param \Magento\Sales\Model\Order\Address $address
     * @return array
     */
    private function mapAddress($address): array
    {
        [$streetLine1, $streetLine2] = $this->extractStreetLines($address->getStreet());

        return [
            'name' => $address->getFirstname() ?: '',
            'surname' => $address->getLastname() ?: '',
            'street' => $streetLine1,
            'city' => $address->getCity() ?: '',
            'postalCode' => $address->getPostcode() ?: '',
            'country' => $address->getCountryId() ?: '',
            'county' => $address->getRegion() ?: '',
            'appartmentNumber' => $streetLine2 ?: ''
        ];
    }

    /**
     * Split a Magento street value (array or string) into line 1 and line 2.
     *
     * @param array|string|null $street
     * @return array [string $line1, string $line2]
     */
    private function extractStreetLines($street): array
    {
        $line1 = is_array($street) && isset($street[0]) ? $street[0] : (is_string($street) ? $street : '');
        $line2 = is_array($street) && isset($street[1]) ? $street[1] : '';

        return [$line1, $line2];
    }

    /**
     * Resolve the display pair (payment_method, payment_method_title) for the order.
     *
     * Case A (SimplyIN, method === 'simplyin_payment'): rebuilt from the payment
     * additional_information written by CheckPaymentStatus / PlaceMagentoOrder
     * (provider name, method id/name, card last4) via the shared display contract.
     * When those keys are absent — e.g. a first sync that races CheckPaymentStatus
     * before the status callback lands — the provider collapses to "Simply.IN" and
     * the title falls back gracefully; a later sync enriches it.
     *
     * Case B (native Magento payment): payment_method is the method-instance
     * title (code fallback); payment_method_title carries the method code only
     * when genuinely different — never a duplicate (empty otherwise).
     *
     * @param \Magento\Sales\Model\Order $order
     * @return array [string $paymentMethod, string $paymentMethodTitle]
     */
    private function getPaymentInfo($order): array
    {
        $payment = $order->getPayment();
        if (!$payment) {
            return ['', ''];
        }

        $method = (string) $payment->getMethod();

        if ($method === 'simplyin_payment') {
            $providerName = (string) $payment->getAdditionalInformation('simplyin_payment_provider_name');
            $methodId = (string) $payment->getAdditionalInformation('simplyin_payment_method');
            if (trim($methodId) === '') {
                // Written at order placement (PlaceMagentoOrder), so it is available
                // even before the CheckPaymentStatus callback lands.
                $methodId = (string) $payment->getAdditionalInformation('simplyin_payment_type');
            }
            $methodName = (string) $payment->getAdditionalInformation('simplyin_payment_method_name');
            $last4 = (string) $payment->getAdditionalInformation('simplyin_card_last4');

            return [
                $this->paymentDisplayFormatter->composeSimplyinMethod($providerName),
                $this->paymentDisplayFormatter->composeSimplyinTitle($methodId, $last4, $methodName),
            ];
        }

        // Case B — native payment method.
        $title = '';
        try {
            $title = (string) $payment->getMethodInstance()->getTitle();
        } catch (\Exception $e) {
            $this->logger->debug('SimplyIN: could not resolve native payment title: ' . $e->getMessage());
        }

        return $this->paymentDisplayFormatter->composeNativeDisplay($title, $method);
    }

    /**
     * Map Magento order status to SimplyIN status
     *
     * @param string $status
     * @return string
     */
    private function mapOrderStatus(string $status): string
    {
        $statusMap = [
            'pending' => 'pending',
            'processing' => 'processing',
            'complete' => 'completed',
            'closed' => 'completed',
            'canceled' => 'cancelled',
            'holded' => 'on-hold'
        ];

        return $statusMap[$status] ?? 'pending';
    }
}
