<?php

namespace EasySales\Integrari\Core\Transformers;

use EasySales\Integrari\Core\EasySales;
use EasySales\Integrari\Helper\Data;
use Magento\Framework\Stdlib\DateTime;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\Data\OrderItemInterface;
use Magento\Framework\App\ObjectManager;

class Order extends BaseTransformer
{
    /**
     * @var Data
     */
    private $helperData;

    /**
     * @var DateTime
     */
    private $dateTime;

    /**
     * Order constructor.
     *
     * @param Data $helperData
     * @param DateTime $dateTime
     */
    public function __construct(Data $helperData, DateTime $dateTime)
    {
        $this->helperData = $helperData;
        $this->dateTime = $dateTime;
    }

    public function transform(OrderInterface $order)
    {
        try {
            $code = $order->getPayment()->getMethodInstance()->getCode();
        } catch (\Exception $exception) {
            $code = 'unknown';
        }

        $paymentMethods = [
            'checkmo' => 2,
            'free' => 1,
            'purchaseorder' => 1,
            'banktransfer' => 3,
            'cashondelivery' => 2,
            'authorizenet' => 1,
            'paypal_express' => 1,
            'paypal_direct' => 1,
            'unknown' => 4,
            'zitec_dpd_cashondelivery' => 2
        ];

        $statuses = [
            'pending' => 1,
            'canceled' => 0,
            'closed' => 3,
            'complete' => 3,
            'suspected_fraud' => 4,
            'payment_review' => 2,
            'on_hold' => 4,
            'paypal_canceled_reversal' => 0,
            'paypal_reversed' => 0,
            'pending_payment' => 1,
            'pending_paypal' => 1,
            'processing' => 4
        ];

        $shippingTaxRate = 0;
        if ($order->getShippingAmount() > 0) {
            $shippingTaxRate = ($order->getShippingTaxAmount() / $order->getShippingAmount()) * 100;
        }

        $incrementId = $order->getIncrementId();

        $entityId = $order->getEntityId();

        $this->data = [
            'order_id' => $incrementId,
            'external_id' => $order->getExtOrderId(),
            'invoice_series' => $this->helperData->getGeneralConfig('invoice_series', null),
            'order_date' => $this->dateTime->formatDate($order->getCreatedAt()),
            'order_total' => (float)$order->getGrandTotal(),
            'status' => isset($statuses[$order->getStatus()]) ? $statuses[$order->getStatus()] : 1,
            'payment_mode' => isset($paymentMethods[$code]) ? $paymentMethods[$code] : 1,
            'shipment_tax' => (float)$order->getShippingInclTax(),
            'observations' => $order->getCustomerNote(),
            'total_vouchers' => (float)$order->getDiscountAmount(),
            'customer' => $this->getCustomer($order, $order->getBillingAddress()->getData()),
            'billing_address' => $this->getAddress($order->getBillingAddress()->getData()),
            'shipping_address' => $order->getShippingAddress() ? $this->getAddress($order->getShippingAddress()->getData()) : null,
            'order_products' => $this->getOrderProducts($order),
            'shipment' => [
                'tax' => $shippingTaxRate,
                'price_with_tax' => $order->getShippingInclTax(),
            ],
        ];

        $shippingMethod = (string)$order->getShippingMethod();
        $shippingDescription = (string)$order->getShippingDescription();

        $lockerId = null;
        $courierName = null;

        if (strpos($shippingMethod, 'ruch_') !== false) {
            if (strpos($shippingDescription, 'Orlen') !== false) {
                $lockerId = $this->getRuchPickupPointCode($entityId);
                $courierName = 'OrlenPaczka';
            }
        } else if ($shippingMethod === 'aurorapaczkomaty_aurorapaczkomaty') {
            if (strpos($shippingDescription, 'InPost') !== false) {
                $lockerId = $this->getParcelOrderCode($incrementId);
                $courierName = 'InPost';
            }
        } else if ($shippingMethod === 'auroradpdpickup_auroradpdpickup') {
            if (strpos($shippingDescription, 'DPD') !== false) {
                $lockerId = $this->getParcelOrderCode($incrementId);
                $courierName = 'DPDPL';
            }
        } else if($order->getSamedaycourierLocker()){
            $lockerInfo = json_decode($order->getSamedaycourierLocker(), true);
            if (isset($lockerInfo['lockerId'])) {
                $lockerId = $lockerInfo['lockerId'];
                $courierName = 'SameDay';
            }
        }

        if ($lockerId && $courierName) {
            $this->data['shipping_method'] = 'Locker';
            $this->data['delivery_data'] = [
                'courier_name' => $courierName,
                'delivery_location_id' => $lockerId
            ];
        }

        if($shippingDescription){
            $this->data['delivery_notes'] = $shippingDescription;
        }

        $vouchers = $this->getVouchers($order);
        if ($vouchers !== null) {
            $this->data['vouchers'] = $vouchers;

            $totalVouchers = 0.0;
            foreach ($vouchers as $voucher) {
                if (isset($voucher['price_with_tax'])) {
                    $totalVouchers += (float)$voucher['price_with_tax'];
                }
            }
            $this->data['total_vouchers'] = $totalVouchers;
        }

        return $this;
    }

    /**
     * Build the vouchers payload combining cart-rule discounts and gift-card
     * redemptions across the gift-card modules we support. Returns null when
     * no gift card is detected so the legacy total_vouchers field continues
     * to drive discount handling for orders with cart-rule discounts only.
     *
     * @param OrderInterface $order
     * @return array|null
     */
    private function getVouchers(OrderInterface $order)
    {
        $giftCards = $this->collectGiftCards($order);
        if (empty($giftCards)) {
            return null;
        }

        $vouchers = [];

        $discountAmount = abs((float)$order->getDiscountAmount());
        if ($discountAmount > 0) {
            $vouchers[] = [
                'name' => 'Discount',
                'price_with_tax' => $discountAmount,
            ];
        }

        foreach ($giftCards as $giftCard) {
            $vouchers[] = $giftCard;
        }

        return !empty($vouchers) ? $vouchers : null;
    }

    /**
     * Scan the order for gift-card amounts written by any of the supported
     * gift-card modules and return them as voucher rows. The probe iterates
     * over a known list of (amount, codes) field pairs so we cover Magento's
     * built-in GiftCardAccount module plus the most common third-party
     * Magento 2 gift-card extensions (Mageplaza, BSS, Anowave/Aheadworks,
     * Mageworx, Amasty, Mirasvit, Sparsh, FME).
     *
     * @param OrderInterface $order
     * @return array
     */
    private function collectGiftCards(OrderInterface $order)
    {
        $signatures = [
            // [amount field on $order->getData(), codes field, default label]
            ['gift_cards_amount', 'gift_cards', 'Gift Card'],
            ['mp_gift_cards_amount', 'mp_gift_cards', 'Gift Card'],
            ['mw_gift_cards_amount', 'mw_gift_cards', 'Gift Card'],
            ['am_giftcard_amount_used', 'am_gift_cards', 'Gift Card'],
            ['aw_giftcard_amount', 'aw_giftcard_codes', 'Gift Card'],
            ['bss_giftcard_amount', 'bss_giftcard_codes', 'Gift Card'],
            ['gift_card_amount', 'gift_card_codes', 'Gift Card'],
            ['sparsh_giftcard_amount', 'sparsh_giftcard_codes', 'Gift Card'],
            ['fme_giftcard_amount', 'fme_giftcard_codes', 'Gift Card'],
        ];

        $orderData = $order->getData();
        $cards = [];

        foreach ($signatures as $signature) {
            list($amountKey, $codesKey, $label) = $signature;

            if (!isset($orderData[$amountKey])) {
                continue;
            }

            $totalAmount = abs((float)$orderData[$amountKey]);
            if ($totalAmount <= 0) {
                continue;
            }

            $entries = $this->parseGiftCardCodes(
                isset($orderData[$codesKey]) ? $orderData[$codesKey] : null
            );

            if (!empty($entries)) {
                foreach ($entries as $entry) {
                    $amount = $this->extractGiftCardAmount($entry);
                    if ($amount <= 0) {
                        continue;
                    }

                    $code = $this->extractGiftCardCode($entry);

                    $cards[] = [
                        'name' => $code ? $label . ' #' . $code : $label,
                        'price_with_tax' => $amount,
                    ];
                }
            } else {
                $cards[] = [
                    'name' => $label,
                    'price_with_tax' => $totalAmount,
                ];
            }
        }

        return $cards;
    }

    /**
     * Decode a serialized/JSON list of gift-card entries written by one of
     * the supported modules. Returns [] when the value is empty or cannot
     * be decoded.
     *
     * @param mixed $raw
     * @return array
     */
    private function parseGiftCardCodes($raw)
    {
        if (is_array($raw)) {
            return $raw;
        }

        if (!$raw || !is_string($raw)) {
            return [];
        }

        $decoded = json_decode($raw, true);
        if (is_array($decoded)) {
            return $decoded;
        }

        $decoded = @unserialize($raw, ['allowed_classes' => false]);
        if (is_array($decoded)) {
            return $decoded;
        }

        return [];
    }

    /**
     * Pull the amount from a gift-card entry. Different modules use different
     * keys (Magento_GiftCardAccount uses 'a', Mageplaza uses 'amount', others
     * use 'balance' or 'value') so we probe the common ones.
     *
     * @param mixed $entry
     * @return float
     */
    private function extractGiftCardAmount($entry)
    {
        if (!is_array($entry)) {
            return 0.0;
        }

        foreach (['a', 'amount', 'applied_balance', 'used_amount', 'value', 'balance'] as $key) {
            if (isset($entry[$key]) && is_numeric($entry[$key])) {
                return abs((float)$entry[$key]);
            }
        }

        return 0.0;
    }

    /**
     * Pull the code/identifier from a gift-card entry, again probing the
     * common keys used across modules.
     *
     * @param mixed $entry
     * @return string|null
     */
    private function extractGiftCardCode($entry)
    {
        if (!is_array($entry)) {
            return null;
        }

        foreach (['c', 'code', 'giftcard_code', 'gift_card_code'] as $key) {
            if (!empty($entry[$key]) && is_scalar($entry[$key])) {
                return (string)$entry[$key];
            }
        }

        return null;
    }

    /**
     * @param $orderId
     * @return null
     */
    public function getParcelOrderCode($incrementId)
    {
        $resourceDB = ObjectManager::getInstance()->get('Magento\Framework\App\ResourceConnection');
        $connection = $resourceDB->getConnection();

        $parcelOrders = [];

        try {
            $dataTable = $resourceDB->getTableName('parcel_orders');

            $sql = "SELECT * FROM " . $dataTable . " WHERE order_increment_id = ?";

            $parcelOrders = $connection->fetchAll($sql, $incrementId);
        } catch (\Exception $e) {
            //
        }

        if (!empty($parcelOrders)) {
            foreach ($parcelOrders as $parcelOrder) {
                $lockerId = $parcelOrder['parcel_id'] ?? null;

                if ($lockerId) {
                    return $lockerId;
                }
            }
        }

        return null;
    }

    public function getRuchPickupPointCode($entityId)
    {
        $resourceDB = ObjectManager::getInstance()->get('Magento\Framework\App\ResourceConnection');
        $connection = $resourceDB->getConnection();

        $ordersData = [];

        try {
            $dataTable = $resourceDB->getTableName('sales_order');
            $sql = "SELECT entity_id, ruch_destinationcode FROM " . $dataTable . " WHERE entity_id = ?";
            $ordersData = $connection->fetchAll($sql, $entityId);
        } catch (\Exception $e) {
            //
        }

        if (!empty($ordersData)) {
            foreach ($ordersData as $order) {
                $lockerId = $order['ruch_destinationcode'] ?? null;

                if ($lockerId) {
                    return $lockerId;
                }
            }
        }

        return null;
    }

    /**
     * @param $order
     * @param $address
     * @return array
     */
    public function getCustomer($order, $address)
    {
        $name = $order['customer_firstname'] . ' ' . $order['customer_lastname'];

        if (!$order['customer_firstname'] || !$order['customer_lastname']) {
            $name = $address['firstname'] . ' ' . $address['lastname'];
        }

        return [
            'name' => $name,
            'company_name' => $address['company'],
            'phone' => $address['telephone'],
            'email' => $order['customer_email'],
            'fax' => $address['fax'],
            'identification_number' => null,
            'legal_entity' => $address['company'] ? 1 : 0,
            'bank' => null,
            'iban' => null,
            'vat_id' => $address['vat_id'],
            'registration_number' => null,
            'vat_payer' => null,
        ];
    }


    /**
     * @param $address
     * @return array
     */
    public function getAddress($address)
    {
        return [
            'name' => $address['firstname'] . ' ' . $address['lastname'],
            'phone' => $address['telephone'],
            'country' => $address['country_id'],
            'county' => $this->helperData->replaceSpecialChars((string)$address['region']),
            'city' => $address['city'],
            'street' => $address['street'],
            'postal_code' => $address['postcode']
        ];
    }

    /**
     * @param $order
     * @return array
     */
    public function getOrderProducts($order)
    {
        $products = [];

        $productsData = $order->getAllVisibleItems();

        /** @var OrderItemInterface $product */
        foreach ($productsData as $product) {
            if ($product->getParentItem()) {
                continue;
            }

            $quantity = $product->getQtyOrdered();

            $unitPrice = $product->getPriceInclTax() / (1 + ((int) $product->getTaxPercent()) / 100);
            $unitPrice = $unitPrice == null ? 0 : round($unitPrice, EasySales::DECIMAL_PRECISION);

            if ($product->getProductType() === 'bundle' && count($product->getChildrenItems())) {
                foreach ($product->getChildrenItems() as $childItem) {
                    $id = $childItem->getProductId();
                    $name = $childItem->getName();
                    $quantity = $childItem->getQtyOrdered();

                    $unitPrice = $childItem->getPriceInclTax() / (1 + ((int) $childItem->getTaxPercent()) / 100);
                    $unitPrice = $unitPrice == null ? 0 : round($unitPrice, EasySales::DECIMAL_PRECISION);

                    $products[] = [
                        'product_website_id' => $id ? $id : $product->getProductId(),
                        'sku' => $childItem->getSku(),
                        'name' => $name ? $name : $product->getName(),
                        'quantity' => (float)$quantity,
                        'price' => $unitPrice,
                        'total' => $unitPrice * $quantity,
                        'tax' => (int)$childItem->getTaxPercent()
                    ];
                }

                continue;
            }

            $properties = null;
            if ($product->getProductType() === 'configurable') {
                $productData = $product->getData();
                if (!empty($productData['product_options']['attributes_info'])) {
                    foreach ($productData['product_options']['attributes_info'] as $data) {
                        $properties[] = [
                            'label' => $data['label'],
                            'value' => $data['value'],
                        ];
                    }
                }
            }

            $orderProductWebsiteId = $product->getProductId();
            $orderProductWebsiteSku = $product->getSku();

            // When product is configurable, we need to get the child product id because magento is returning db id
            if ($product->getProductType() === 'configurable') {
                foreach ($product->getChildrenItems() ?? [] as $childItem) {
                    if($childItem->getProductType() === 'simple'){
                        if($childItem->getSku() === $orderProductWebsiteSku){
                            $orderProductWebsiteId = $childItem->getProductId();
                        }
                    }
                }
            }

            $products[] = [
                'product_website_id' => $orderProductWebsiteId,
                'sku' => $orderProductWebsiteSku,
                'name' => $product->getName(),
                'quantity' => (float)$quantity,
                'price' => $unitPrice,
                'total' => $unitPrice * $quantity,
                'tax' => (int)$product->getTaxPercent(),
                'properties' => $properties,
            ];
        }

        return $products;
    }
}
