<?php

/**
 * @copyright   Copyright (C) 2005 - 2013 Michael Richey. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\Plugin\Content\APAAPI\Extension;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
use Joomla\CMS\Cache\Controller\OutputController;
use Joomla\CMS\Language\Text;
use Amazon\ProductAdvertisingAPI\v1\ApiException;
use Amazon\ProductAdvertisingAPI\v1\com\amazon\paapi5\v1\api\DefaultApi;
use Amazon\ProductAdvertisingAPI\v1\com\amazon\paapi5\v1\GetItemsRequest;
use Amazon\ProductAdvertisingAPI\v1\com\amazon\paapi5\v1\GetItemsResource;
use Amazon\ProductAdvertisingAPI\v1\com\amazon\paapi5\v1\PartnerType;
use Amazon\ProductAdvertisingAPI\v1\com\amazon\paapi5\v1\ProductAdvertisingAPIClientException;
use Amazon\ProductAdvertisingAPI\v1\Configuration;
use GuzzleHttp;
use stdClass;

defined('_JEXEC') or die;

final class APAAPI extends CMSPlugin 
{
    protected $app;
    private $_cache = false;
    private $_debug = false;
    private $_lifetime;
    private $items = [];
    private $errors = [];

    /**
     * Constructor.
     *
     * @param   object  &$subject  The object to observe.
     * @param   array   $config    An optional associative array of configuration settings.
     *
     * @since   1.0
     */
    // public function __construct(&$subject, $config = array())
    // {
    //     parent::__construct($subject, $config);
    // }

	/**
	 * Returns an array of events this subscriber will listen to.
	 *
	 * @return  array
	 */
	public static function getSubscribedEvents(): array
	{
		return [
			'onContentPrepare' => 'onContentPrepare',
		];
	}

    public function onContentPrepare($context, &$article, &$params, $page = 0) {
        $this->app = Factory::getApplication();
        $this->_debug = $this->app->get('debug',0)?true:false;
        if($this->app->get('caching',1) && $this->params->get('caching',1)) {
            $this->_cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController('output',['defaultgroup'=>'plg_content_apaapi']);
            $cachetime = (int)$this->params->get('cache-time',0)?:$this->app->get('cachetime', 15);
            $this->_lifetime = $cachetime;
            $this->_cache->setCaching(1);
        }
        // only operate if the in site and article context
        if(!$this->app->isClient('site') || $context != 'com_content.article') {
            return true;
        }

        // only operate if the article has a {amazon code present
        if (strpos($article->text, '{amazon') === false) {
            return;
        }

        $this->app->getLanguage()->load('plg_content_apaapi', JPATH_ADMINISTRATOR);

        // if 'testing' is enabled, search the article keywords for 'paapi' or exit, replacing {amazon(.*?)} with ''
        if ($this->params->get('testing', 0) && strpos($article->metakey, 'paapi') === false) {
            $article->text = preg_replace('/{amazon(.*?)}\s*/', '', $article->text);
            return;
        }
        require_once __DIR__.'/vendor/autoload.php';

        
        // get a list of all the {amazon tags in the article
        preg_match_all('/{amazon\s+(.*?)}/', $article->text, $matches);

        // return if no matches
        if (empty($matches[1])) {
            return;
        }

        // full match example: {amazon asin=1234567890 template=default}
        $matchstore = [];
        $fetch = [];

        // loop through the matches
        foreach ($matches[1] as $id=>$match) {
            // error_log('APAAPI MATCH: ' . $match);
            $o = new stdClass();
            // parse the attributes
            $attributes = [];
            preg_match_all('/(\w+)=(\w+)/', $match, $attributes);

            // create an array of the attributes
            $o->params = [];
            foreach ($attributes[1] as $i => $key) {
                $o->params[$key] = $attributes[2][$i];
            }
            $o->match = $matches[0][$id];
            $matchstore[$o->match] = $o;
            
            if($this->_cache !== false && $this->_cache->contains($o->params['asin'])) {
                if($this->_debug) error_log($o->params['asin'] . " found in cache");
                $cacheditem = $this->_cache->get($o->params['asin']);
                $this->items[$o->params['asin']] = $cacheditem;
            } else {
                $fetch[] = $o->params['asin'];
                if($this->_debug) error_log($o->params['asin'] . " not found in cache");
            }
        }

        $this->items += $this->getItems($fetch)??[];
        $errors = false;
        if(count($this->errors)) {
            $errors = $this->renderErrors();
            if($this->_debug) error_log("Errors found: " . implode(', ', $this->errors));
        }   

        foreach($matchstore as $match => $o) {
            // if we have errors, render them
            if($errors) {
                $article->text = str_replace($match, $errors, $article->text);
                continue;
            }
            if($this->_debug) error_log("replacing " . $match . " with " . $o->params['asin']);             
            $template = $o->params['template'] ?? $this->params->get('tmpl','default');
            $product = $this->getProduct($o->params['asin'],$template);
            // replace the tag with the product
            $article->text = str_replace($match, $product, $article->text);
        }
    }

    private function renderErrors()
    {
        if(!count($this->errors)) {
            return '';
        }
        $html = '<div class="alert alert-danger">';
        $html .= '<h4>' . Text::_('PLG_CONTENT_APAAPI_ERROR_TITLE') . '</h4>';
        $html .= '<ul>';
        foreach ($this->errors as $error) {
            $html .= '<li>' . htmlspecialchars($error, ENT_QUOTES, 'UTF-8') . '</li>';
        }
        $html .= '</ul></div>';
        echo $html;
    }

    private function getProduct($asin,$template='default')
    {
        $product = $this->items[$asin];
        if(!$product) {
            return '';
        }
        $path = PluginHelper::getLayoutPath('content', 'apaapi', $template); 
        ob_start();      
        // include(JPATH_PLUGINS. '/content/apaapi/tmpl/' . $template . '.php');
        include $path;
        return ob_get_clean();
    }

    private function getItems($itemIds=[])
    {
        if(!count($itemIds)) {
            return [];
        }
        $regions = array(
            "au"=>["us-west-2","webservices.amazon.com.au"],
            "be"=>["eu-west-1","webservices.amazon.com.be"],
            "br"=>["us-east-1","webservices.amazon.com.br"],
            "ca"=>["us-east-1","webservices.amazon.ca"],
            "eg"=>["eu-west-1","webservices.amazon.eg"],
            "fr"=>["eu-west-1","webservices.amazon.fr"],
            "de"=>["eu-west-1","webservices.amazon.de"],
            "in"=>["eu-west-1","webservices.amazon.in"],
            "it"=>["eu-west-1","webservices.amazon.it"],
            "jp"=>["us-west-2","webservices.amazon.co.jp"],
            "mx"=>["us-east-1","webservices.amazon.com.mx"],
            "nl"=>["eu-west-1","webservices.amazon.nl"],
            "pl"=>["eu-west-1","webservices.amazon.pl"],
            "sg"=>["us-west-2","webservices.amazon.sg"],
            "sa"=>["eu-west-1","webservices.amazon.sa"],
            "es"=>["eu-west-1","webservices.amazon.es"],
            "se"=>["eu-west-1","webservices.amazon.se"],
            "tr"=>["eu-west-1","webservices.amazon.com.tr"],
            "ae"=>["eu-west-1","webservices.amazon.ae"],
            "gb"=>["eu-west-1","webservices.amazon.co.uk"],
            "us"=>["us-east-1","webservices.amazon.com"]
        );
        $config = new Configuration();
        # Please add your access key here
        $access_key = $this->params->get('access_key', '');
        if(empty($access_key)) {
            $this->errors[] = Text::_('PLG_CONTENT_APAAPI_ERROR_MISSING_ACCESS_KEY');
        }
        $config->setAccessKey($access_key);
        # Please add your secret key here
        $secret_key = $this->params->get('secret_key', '');
        if(empty($secret_key)) {
            $this->errors[] = Text::_('PLG_CONTENT_APAAPI_ERROR_MISSING_SECRET_KEY');
        }
        $config->setSecretKey($this->params->get('secret_key'));
    
        # Please add your partner tag (store/tracking id) here
        $partnerTag = $this->params->get('associate_tag');
        if(empty($partnerTag)) {
            $this->errors[] = Text::_('PLG_CONTENT_APAAPI_ERROR_MISSING_PARTNER_TAG');
        }

        $country = $this->params->get ('hostregion','us');
        $config->setHost($regions[$country][1]);
        $config->setRegion($regions[$country][0]);

        $apiInstance = new DefaultApi(
            /*
             * If you want use custom http client, pass your client which implements `GuzzleHttp\ClientInterface`.
             * This is optional, `GuzzleHttp\Client` will be used as default.
             */
            new GuzzleHttp\Client(),
            $config
        );


        /*
        * Choose resources you want from GetItemsResource enum
        * For more details, refer: https://webservices.amazon.com/paapi5/documentation/get-items.html#resources-parameter
        */
        $resources = [
            GetItemsResource::ITEM_INFOTITLE,
            GetItemsResource::ITEM_INFOBY_LINE_INFO,
            GetItemsResource::IMAGESPRIMARYLARGE,
            GetItemsResource::ITEM_INFOFEATURES,
            GetItemsResource::CUSTOMER_REVIEWSCOUNT,
            GetItemsResource::CUSTOMER_REVIEWSSTAR_RATING,
            GetItemsResource::OFFERSLISTINGSPRICE
        ];

        # Forming the request
        $getItemsRequest = new GetItemsRequest();
        $getItemsRequest->setItemIds($itemIds);
        $getItemsRequest->setPartnerTag($partnerTag);
        $getItemsRequest->setPartnerType(PartnerType::ASSOCIATES);
        $getItemsRequest->setResources($resources);

        # Validating request
        $invalidPropertyList = $getItemsRequest->listInvalidProperties();
        $length = count($invalidPropertyList);
        if ($length > 0) {
            echo "Error forming the request", PHP_EOL;
            // $this->app->enqueueMessage("Error forming the request", 'error');
            foreach ($invalidPropertyList as $invalidProperty) {
                echo $invalidProperty, PHP_EOL;
                // $this->app->enqueueMessage($invalidProperty, 'error');
            }
            return null;
        }

        # Sending the request
        try {
            $getItemsResponse = $apiInstance->getItems($getItemsRequest);

            // echo 'API called successfully', PHP_EOL;
            // echo 'Complete Response: ', $getItemsResponse, PHP_EOL;

            # Parsing the response
            if ($getItemsResponse->getItemsResult() !== null) {
                if($this->_debug) error_log('Printing all item information in ItemsResult:');
                if ($getItemsResponse->getItemsResult()->getItems() !== null) {
                    $responseList = $this->parseResponse($getItemsResponse->getItemsResult()->getItems());

                    foreach ($itemIds as $itemId) {
                        if($this->_debug) error_log('Printing information about the itemId: ' . $itemId);
                        $item = $responseList[$itemId];
                    }
                }
            }
            if ($getItemsResponse->getErrors() !== null) {
                if($this->_debug) error_log('Error code: ' . $getItemsResponse->getErrors()[0]->getCode());
                if($this->_debug) error_log('Error message: ' . $getItemsResponse->getErrors()[0]->getMessage());
            }
        } catch (ApiException $exception) {
            if($this->_debug) error_log("HTTP Status Code: " . $exception->getCode());
            if($this->_debug) error_log("Error Message: " . $exception->getMessage());
            if ($exception->getResponseObject() instanceof ProductAdvertisingAPIClientException) {
                $errors = $exception->getResponseObject()->getErrors();
                foreach ($errors as $error) {
                    $this->errors[] = Text::sprintf('PLG_CONTENT_APAAPI_ERROR_API_CODE', $error->getCode());
                    $this->errors[] = Text::sprintf('PLG_CONTENT_APAAPI_ERROR_API_MESSAGE', $error->getMessage());
                    if($this->_debug) error_log("Error Type: " . $error->getCode());
                    if($this->_debug) error_log("Error Message: " . $error->getMessage());
                }
            } else {
                if($this->_debug) error_log("Error response body: " . $exception->getResponseBody());
            }
            return null;
        } catch (Exception $exception) {
            $this->errors[] = Text::sprintf('PLG_CONTENT_APAAPI_ERROR_API_MESSAGE', $exception->getMessage());
            if($this->_debug) error_log("Error Message: " . $exception->getMessage());
            return null;
        }
        return $responseList;
    }
    private function parseResponse($items)
    {
        $mappedResponse = [];
        foreach ($items as $item) {
            $iteminfo = $item->getItemInfo();
            $features = $iteminfo->getFeatures();
            $product = new stdClass();
            $product->asin = $item->getASIN();
            $product->url = $item->getDetailPageURL();
            $product->image = $item->getImages()->getPrimary()->getLarge()->getUrl();
            $product->title = $iteminfo->getTitle()->getDisplayValue();
            $product->features = $features?$features->getDisplayValues():[];
            $mappedResponse[$product->asin] = $product;
            if($this->_cache !== false) {
                if($this->_debug) error_log('caching ' . $product->asin . ' for ' . $this->_lifetime . ' minutes');
                $this->_cache->store($product,$product->asin);
            }
        }
        return $mappedResponse;
    }
}
