<?php
/**
 * Magento 2 Google Analytics 4 for Magento 2
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Anowave license that is
 * available through the world-wide-web at this URL:
 * http://www.anowave.com/license-agreement/
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade this extension to newer
 * version in the future.
 *
 * @category 	Anowave
 * @package 	Anowave_Ec
 * @copyright 	Copyright (c) 2025 Anowave (http://www.anowave.com/)
 * @license  	http://www.anowave.com/license-agreement/
 */

namespace Anowave\Ec4\Plugin;

use Anowave\Ec\Model\Cookie\AnalyticsSession;
use Magento\Framework\Event\Observer as EventObserver;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Anowave\Ec\Model\ResourceModel\Transaction\CollectionFactory as TransactionFactory;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\State;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory;
use Magento\Catalog\Model\CategoryRepository;
use Magento\Catalog\Model\ProductFactory;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\Message\ManagerInterface;
use Anowave\Ec4\Helper\Data;
use Anowave\Ec\Model\Logger;

class Refund
{
    /**
     * No category fallback 
     * 
     * @var string
     */
    const FALLBACK_NO_CATEGORY = 'Not set';
    
    /**
     * @var \Anowave\Ec4\Helper\Data
     */
    protected $helper = null;
    
    /**
     * @var \Magento\Framework\Message\ManagerInterface
     */
    protected $messageManager = null;
    
    /**
     * @var \Magento\Catalog\Model\ProductFactory
     */
    protected $productFactory;
    
    /**
     * @var \Magento\Catalog\Model\CategoryRepository
     */
    protected $categoryRepository;
    
    /**
     * @var \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory
     */
    protected $attribute;
    
    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $scopeConfig;
    
    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager = null;
    
    /**
     * @var \Magento\Framework\App\State
     */
    protected $state;
    
    /**
     * @var \Magento\Framework\App\RequestInterface
     */
    protected $request;
    
    /**
     * @var \Anowave\Ec\Model\Logger
     */
    protected $logger;

    /**
     * @var \Anowave\Ec\Model\ResourceModel\Transaction\CollectionFactory
     */
    protected $transactionCollectionFactory;
    
    protected AnalyticsSession $analyticsSession;

    /**
     * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
     */
    private $productCollectionFactory;

    /**
     * Attribute cache (avoids N+1 loads)
     *
     * @var array
     */
    private $attributeCache = [];

    /**
     * Constructor
     * 
     * @param \Anowave\Ec4\Helper\Data $helper
     * @param \Magento\Framework\Message\ManagerInterface $messageManager
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Catalog\Model\ProductFactory $productFactory
     * @param \Magento\Catalog\Model\CategoryRepository $categoryRepository
     * @param \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory $attribute
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Magento\Framework\App\State $state
     * @param \Magento\Framework\App\RequestInterface $request
     * @param \Anowave\Ec\Model\Logger $logger
     */
    public function __construct
    (
        Data $helper,
        ManagerInterface $messageManager,
        StoreManagerInterface $storeManager,
        ProductFactory $productFactory,
        CategoryRepository $categoryRepository,
        AttributeFactory $attribute,
        ScopeConfigInterface $scopeConfig,
        state $state,
        RequestInterface $request,
        Logger $logger,
        TransactionFactory $transactionCollectionFactory,
        AnalyticsSession $analyticsSession,
        ProductCollectionFactory $productCollectionFactory
    )
    {
        /**
         * Set helper
         *
         * @var \Anowave\Ec4\Helper\Data $helper
         */
        $this->helper = $helper;
        
        /**
         * Set message manager
         *
         * @var \Magento\Framework\Message\ManagerInterface $_messageManager
         */
        $this->messageManager = $messageManager;
        
        /**
         * Set store manager
         *
         * @var \Magento\Store\Model\StoreManagerInterface $storeManager
         */
        $this->storeManager = $storeManager;
        
        /**
         * Set product factory
         *
         * @var \Magento\Catalog\Model\ProductFactory $productFactory
         */
        $this->productFactory = $productFactory;
        
        /**
         * Set attribute
         *
         * @var \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory
         */
        $this->attribute = $attribute;
        
        /**
         * Set category repository
         *
         * @var \Magento\Catalog\Model\CategoryRepository $categoryRepository
         */
        $this->categoryRepository = $categoryRepository;
        
        /**
         * Set scope config
         *
         * @var \Anowave\Ec\Observer\Refund $scopeConfig
         */
        $this->scopeConfig = $scopeConfig;
        
        /**
         * Set state 
         * 
         * @var \Magento\Framework\App\State $state
         */
        $this->state = $state;
        
        /**
         * Set request 
         * 
         * @var \Anowave\Ec4\Plugin\Refund $request
         */
        $this->request = $request;
        
        /**
         * Set logger 
         * 
         * @var \Anowave\Ec\Model\Logger $logger
         */
        $this->logger = $logger;

        /**
         * Set transaction collection factory 
         * 
         * @var \Anowave\Ec\Model\ResourceModel\Transaction\CollectionFactory
         */
        $this->transactionCollectionFactory = $transactionCollectionFactory;

        $this->analyticsSession = $analyticsSession;

        /**
         * Set product collection factory
         */
        $this->productCollectionFactory = $productCollectionFactory;
    }
    
    /**
     * Modify exeucte method 
     * 
     * @param \Anowave\Ec\Observer\Refund $context
     * @param callable $proceed
     * @param EventObserver $observer
     * @return unknown|boolean
     */
    public function aroundExecute(\Anowave\Ec\Observer\Refund $context, callable $proceed, EventObserver $observer)
    {
        if (!$this->trackRefund())
        {
            return $proceed($observer);
        }

        /**
         * Get credit memo details 
         * 
         * @var array $creditmemo
         */
        $creditmemo = $observer->getEvent()->getCreditmemo();

        if ($creditmemo)
        {
            $order = $creditmemo->getOrder();
        }
        else 
        {
            return $proceed($observer);
        }

        if ($this->refund($order, $creditmemo))
        {
            $store = (int) $order->getStoreId();
            
            $this->messageManager->addSuccessMessage("Refund for order {$order->getIncrementId()} tracked to Google Analytics 4 {$this->helper->getMeasurementId($store)} successfully.");
            
            return true;
        }
        
        return $proceed($observer);
    }
    
    /**
     * Refund order 
     * 
     * @param \Magento\Sales\Model\Order $order
     * @return boolean
     */
    public function refund(\Magento\Sales\Model\Order $order, ?\Magento\Sales\Model\Order\Creditmemo $memo = null)
    {
        /**
         * Get store 
         * 
         * @var int $store
         */
        $store = (int) $order->getStoreId();

        /**
         * Get measurement id 
         * 
         * @var string $measurement_id
         */
        $measurement_id = $this->helper->getMeasurementId($store);
        
        /**
         * Get measurement secret
         * 
         * @var string $measurement_api_secret
         */
        $measurement_api_secret = $this->helper->getMeasurementApiSecret($store);
        
        /**
         * Create random client id
         */
        $client_id = bin2hex(random_bytes(32));


        $session_id = null;

        /**
         * Load original transaction
         */
        $transaction = $this->getTransaction($order->getId());

        /**
         * Contruct client_id from origianal cookie
         */
        if ($transaction && isset($transaction['ec_cookie_ga']))
        {
            $ga = explode(chr(46),$transaction['ec_cookie_ga']);

            if (count($ga) > 3)
            {
                $client_data = array_slice($ga, -2);

                /**
                 * Reconstruct client_id
                 */
                $client_id = join(chr(46), $client_data);
            }
        }

        /**
         * Contruct client_id from origianal cookie
         */
        if ($transaction && isset($transaction['ec_cookie_fpid']) && !empty($transaction['ec_cookie_fpid']))
        {
            $client_id = $transaction['ec_cookie_fpid'];

            $prefix = 'FPID2.2.';
            if (str_starts_with($client_id, $prefix)) {
                $client_id = substr($client_id, strlen($prefix));
            }
        }

        if ($transaction && isset($transaction['ec_cookie_ga_session']))
        {
            $session_id = $this->analyticsSession->extractSession($transaction['ec_cookie_ga_session']);
        }

        /**
         * Get payload
         * 
         * @var \Closure $payload
         */
        $payload = function(array $events = []) use ($client_id, $session_id)
        {
            $data = 
            [
                'events' => $events
            ];

            if ($client_id)
            {
                $data['client_id'] = $client_id;
            }

            if ($session_id)
            {
                foreach($data['events'] as &$event)
                {
                    $event['params']['session_id'] = $session_id;
                    $event['params']['engagement_time_msec'] = 100;
                }

                unset($event);
                
            }

            return $data;
        };

        if ($measurement_id && $measurement_api_secret)
        {
            $refund = 
            [
                'value'     => $this->helper->getBaseHelper()->getAmountConvert((float) $memo->getGrandTotal(),     $memo->getOrderCurrencyCode(), $this->helper->getBaseHelper()->getCurrencyConvert()),
                'shipping'  => $this->helper->getBaseHelper()->getAmountConvert((float) $memo->getShippingAmount(), $memo->getOrderCurrencyCode(), $this->helper->getBaseHelper()->getCurrencyConvert()),
                'tax'       => $this->helper->getBaseHelper()->getAmountConvert((float) $memo->getTaxAmount(),      $memo->getOrderCurrencyCode(), $this->helper->getBaseHelper()->getCurrencyConvert())
            ];

            $items = [];
            
            /**
             * Default start position
             *
             * @var int
             */
            $index = 1;
            
            if ($order->getTotalRefunded() > 0)
            {
                $products = $this->getProducts($order, $memo);
                
                foreach($products as $product)
                {
                    $item =
                    [
                        'index'         =>          $index,
                        'item_id'       =>          @$product['id'],
                        'item_name'     =>          @$product['name'],
                        'item_brand'    => (string) @$product['brand'],
                        'price'         =>          @$this->helper->getBaseHelper()->getAmountConvert((float)  $product['price'], $memo->getOrderCurrencyCode(), $this->helper->getBaseHelper()->getCurrencyConvert()),
                        'quantity'      => (int)    @$product['quantity']
                    ];
                    
                    $categories = explode(chr(47), (string) @$product['category']);
                    
                    if ($categories)
                    {
                        $category = array_shift($categories);
                        
                        if ($category)
                        {
                            $item['item_category'] = $category;
                        }
                        
                        foreach ($categories as $index => $category)
                        {
                            $key = $index + 2;
                            
                            $item["item_category{$index}"] = $category;
                        }
                    }
                    else 
                    {
                        $item['item_category'] = __(static::FALLBACK_NO_CATEGORY);
                    }
                    
                    
                    $items[] = $item;
                    
                    $index++;
                }

                $data = $payload
                (
                    [
                        [
                            'name' => 'refund',
                            'params' =>
                            [
                                'currency'       => $this->helper->getBaseHelper()->getCurrency(),
                                'transaction_id' => $order->getIncrementId(),
                                'value'          => $this->helper->getBaseHelper()->getAmount((float) $refund['value']),
                                'shipping'	     => $this->helper->getBaseHelper()->getAmount((float) $refund['shipping']),
                                'tax'	         => $this->helper->getBaseHelper()->getAmount((float) $refund['tax']),
                                'affiliation'    => $this->helper->getBaseHelper()->escape
                                (
                                    $this->helper->getBaseHelper()->getStoreName()
                                ),
                                'items' => $items,
                                'traffic_type' => $this->state->getAreaCode()
                            ]
                        ]
                    ]
                );

                $analytics = curl_init("https://www.google-analytics.com/mp/collect?measurement_id={$measurement_id}&api_secret={$measurement_api_secret}");

                curl_setopt_array($analytics, $this->helper->getBaseHelper()->getCurlOptions($data));

                try
                {
                    $response = curl_exec($analytics);

                    if ($this->helper->getBaseHelper()->useDebugMode())
                    {
                        $this->messageManager->addNoticeMessage(json_encode($data, JSON_PRETTY_PRINT));
                    }
                     
                    if (!curl_error($analytics))
                    { 
                        $this->logger->log(json_encode(
                        [
                            'message' => __("Order {$order->getIncrementId()} refunded to {$measurement_id} from {$this->state->getAreaCode()}"),
                            'payload' => $data
                        ]));
                        
                        return true;
                    } 
                }
                catch (\Exception $e) 
                {
                    $this->messageManager->addErrorMessage($e->getMessage());
                    
                    $this->logger->log($e->getMessage());
                }
            }
        }
        
        return false;
    }
    
    /**
     * Get products 
     * 
     * @param \Magento\Sales\Model\Order $order
     * @param array $creditmemo
     * @return array
     */
    public function getProducts(\Magento\Sales\Model\Order $order, ?\Magento\Sales\Model\Order\Creditmemo $memo = null) : array 
    {
        /**
         * Get products 
         * 
         * @var array $products
         */
        $products = [];
        
        /**
         * Map 
         * 
         * @var array $map
         */
        $map = [];

        foreach ($memo->getItems() as $identifier => $entity)
        { 
            if ((int) $entity->getQty() > 0)
            {
                $map[$entity->getOrderItemId()] = (int) $entity->getQty() ;
            }
        }

        /**
         * Batch preload products with category_ids to avoid N+1
         */
        $orderItems = $order->getAllVisibleItems();
        $productIds = [];

        foreach ($orderItems as $item) {
            if ($item->getProduct() && array_key_exists($item->getId(), $map)) {
                $productIds[] = $item->getProduct()->getId();
            }
        }

        $preloadedProducts = [];

        if ($productIds) {
            $productCollection = $this->productCollectionFactory->create()
                ->addFieldToFilter('entity_id', ['in' => $productIds])
                ->addAttributeToSelect(['category_ids']);

            foreach ($productCollection as $product) {
                $preloadedProducts[$product->getId()] = $product;
            }
        }

        foreach ($orderItems as $item)
        {
            try
            {
                if (array_key_exists($item->getId(), $map))
                {
                    $collection = [];

                    /**
                     * Handle edge cases when product does not exists (deleted)
                     */
                    if (!$item->getProduct()) continue;

                    if ($item->getProduct())
                    {
                        $entity = $preloadedProducts[$item->getProduct()->getId()] ?? null;

                        if ($entity) {
                            $collection = $entity->getCategoryIds();
                        }
                    }
                    
                    if ($collection)
                    {
                        $category = $this->categoryRepository->get(end($collection));
                    }
                    else
                    {
                        $category = null;
                    }
                    
                    /**
                     * Get product name
                     */
                    $args = new \stdClass();
                    
                    $args->id 	= $this->helper->getBaseHelper()->getIdentifierItem($item);
                    $args->name = $item->getName();
                    
                    /**
                     * Product variant(s)
                     *
                     * @var []
                     */
                    $variant = [];
                    
                    if ('configurable' === $item->getProductType())
                    {
                        $options = (array) $item->getProductOptions();
                        
                        if (isset($options['info_buyRequest']))
                        {
                            $info = new \Magento\Framework\DataObject($options['info_buyRequest']);
                            
                            /**
                             * Construct variant
                             */
                            foreach ((array) $info->getSuperAttribute() as $id => $option)
                            {
                                /**
                                 * Load attribute model (cached)
                                 *
                                 * @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute
                                 */
                                $attribute = $this->getCachedAttribute((int) $id);
                                
                                if ($attribute->usesSource())
                                {
                                    $variant[] = join(\Anowave\Ec\Helper\Data::VARIANT_DELIMITER_ATT,
                                    [
                                        $this->escape($attribute->getFrontendLabel()),
                                        $this->escape($attribute->getSource()->getOptionText($option))
                                    ]);
                                }
                            }
                        }
                    }
                    
                    $data =
                    [
                        'name' 		=> $this->escape($args->name),
                        'id'		=> $this->escape($args->id),
                        'price' 	=> $item->getPrice(),
                        'quantity' 	=> $map[$item->getId()],
                        'variant'	=> join(\Anowave\Ec\Helper\Data::VARIANT_DELIMITER, $variant)
                    ];
                    
                    if ($category)
                    {
                        $data['category'] = $this->escape($category->getName());
                    }
                    
                    $products[] = $data;
                }
            }
            catch (\Exception $e)
            {
                $this->logger->log($e->getMessage());    
            }
        }
        
        return $products;
    }
    
    /**
     * Escape quotes
     *
     * @param string $string
     */
    /**
     * Get attribute by ID with in-memory cache
     *
     * @param int $attributeId
     * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute
     */
    private function getCachedAttribute(int $attributeId): \Magento\Catalog\Model\ResourceModel\Eav\Attribute
    {
        if (!isset($this->attributeCache[$attributeId])) {
            $this->attributeCache[$attributeId] = $this->attribute->create()->load($attributeId);
        }

        return $this->attributeCache[$attributeId];
    }

    public function escape($data)
    {
        return $this->helper->getBaseHelper()->escape($data);
    }
    
    /**
     * Check if refund tracking is enabled
     *
     * @return boolean
     */
    public function trackRefund() : bool
    {
        return 1 === (int) $this->helper->getConfig('ec/gmp/use_measurement_protocol_refund');
    }

    /**
     * Get trsnaction
     * 
     * @param int $order_id
     * 
     * @return array
     */
    private function getTransaction(int $order_id = 0) : array 
    {
        $collection = $this->transactionCollectionFactory->create()->addFieldToFilter('ec_order_id', $order_id);

        if ($collection->getSize() > 0)
        {
            return $collection->getFirstitem()->getData();
        }

        return [];
    }
}