<?php
/**
 * @author 1902 Software
 * @copyright Copyright © 2023 1902 Software (https://1902software.com/magento/)
 * @package WriteTextAI_WriteTextAI
 */

namespace WriteTextAI\WriteTextAI\Controller\Adminhtml\Edit;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use WriteTextAI\WriteTextAI\Model\ApiManager;
use Magento\User\Model\UserFactory;
use Magento\User\Model\ResourceModel\User\CollectionFactory;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
use Magento\Catalog\Helper\Output as OutputHelper;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use WriteTextAI\WriteTextAI\Helper\Data as HelperData;
use WriteTextAI\WriteTextAI\Helper\Categories\Fields as CategoriesFieldsHelper;
use WriteTextAI\WriteTextAI\Model\Api\Session as ApiSession;
use WriteTextAI\WriteTextAI\Helper\Store as StoreHelper;
use WriteTextAI\WriteTextAI\Model\OptionSource\Filter\Fields;

class History extends Action
{
    /**
     * @var ApiManager
     */
    protected $apiManager;

    /**
     * @var UserFactory
     */
    protected $userFactory;

    /**
     * @var CollectionFactory
     */
    protected $userCollectionFactory;

    /**
     * @var TimezoneInterface
     */
    protected $timezone;

    /**
     * @var ProductCollectionFactory
     */
    protected $productCollectionFactory;

    /**
     * @var OutputHelper
     */
    protected $outputHelper;

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

    /**
     * @var CategoryRepositoryInterface
     */
    protected $categoryRepository;

    /**
     * @var HelperData
     */
    protected $helperData;

    /**
     * @var CategoriesFieldsHelper
     */
    protected $categoriesFieldsHelper;

    /**
     * @var ApiSession
     */
    protected $apiSession;

    /**
     * @var StoreHelper
     */
    protected $storeHelper;

    /**
     * @var CategoryCollectionFactory
     */
    protected $categoryCollectionFactory;

    /**
     * @var array
     */
    protected $users;

    /**
     * @var array
     */
    protected $products = [];

    /**
     * @var array
     */
    protected $categories = [];

    /**
     * @var array
     */
    protected $entities = [];

    /**
     * Constructor
     *
     * @param Context $context
     * @param ApiManager $apiManager
     * @param UserFactory $userFactory
     * @param CollectionFactory $userCollectionFactory
     * @param TimezoneInterface $timezone
     * @param ProductCollectionFactory $productCollectionFactory
     * @param OutputHelper $outputHelper
     * @param ProductRepositoryInterface $productRepository
     * @param CategoryRepositoryInterface $categoryRepository
     * @param HelperData $helperData
     * @param CategoriesFieldsHelper $categoriesFieldsHelper
     * @param ApiSession $apiSession
     * @param StoreHelper $storeHelper
     * @param CategoryCollectionFactory $categoryCollectionFactory
     */
    public function __construct(
        Context $context,
        ApiManager $apiManager,
        UserFactory $userFactory,
        CollectionFactory $userCollectionFactory,
        TimezoneInterface $timezone,
        ProductCollectionFactory $productCollectionFactory,
        OutputHelper $outputHelper,
        ProductRepositoryInterface $productRepository,
        CategoryRepositoryInterface $categoryRepository,
        HelperData $helperData,
        CategoriesFieldsHelper $categoriesFieldsHelper,
        ApiSession $apiSession,
        StoreHelper $storeHelper,
        CategoryCollectionFactory $categoryCollectionFactory
    ) {
        $this->apiManager = $apiManager;
        $this->userFactory = $userFactory;
        $this->userCollectionFactory = $userCollectionFactory;
        $this->timezone = $timezone;
        $this->productCollectionFactory = $productCollectionFactory;
        $this->outputHelper = $outputHelper;
        $this->productRepository = $productRepository;
        $this->categoryRepository = $categoryRepository;
        $this->helperData = $helperData;
        $this->categoriesFieldsHelper = $categoriesFieldsHelper;
        $this->apiSession = $apiSession;
        $this->storeHelper = $storeHelper;
        $this->categoryCollectionFactory = $categoryCollectionFactory;
        parent::__construct($context);
    }

    /**
     * Execute action
     *
     * @return \Magento\Framework\Controller\Result\Json
     */
    public function execute()
    {
        $response = $this->resultFactory->create(ResultFactory::TYPE_JSON);
        $result = [
            'success' => true,
            'entries' => [],
            'next_page_token' => '',
        ];
        
        try {
            $user = $this->apiSession->getCurrentUser();
            $regionIndependentLanguage = $this->apiSession->getRegionIndependentLanguage();
            
            $params = $this->getParams();
            $history = $this->apiManager->getHistory($params, $regionIndependentLanguage);

            if (isset($history['histories']) &&
                is_array($history['histories']) &&
                !empty($history['histories'])
            ) {
                if ($params['type'] === 'Category') {
                    $this->collectCategories($history['histories']);
                } else {
                    $this->collectProducts($history['histories']);
                }
                $entries = $this->groupHistory($history['histories']);
                $result = [
                    'success' => true,
                    'entries' => $entries,
                    'next_page_token' => $history['continuationToken'] ?? '',
                ];
            }
        } catch (\Exception $e) {
            $message = $e->getMessage();
            if (!$message) {
                $message = __(
                    'A system error has occurred. Please try again. If the issue persists,'
                    . ' please contact our support team at support@writetext.ai.'
                );
            }
            
            $result = [
                'success' => false,
                'message' => $message
            ];
        }

        $response->setData($result);

        return $response;
    }

    /**
     * Group history by timestamp, user, and action
     *
     * @param array $history
     * @return array
     */
    protected function groupHistory($history)
    {
        /**
         * group by timestamp, user, and action
         */
        $entries = [];
        $storeId = $this->getRequest()->getParam('store_id', 0);
        $historyType = $this->getRequest()->getParam('history_type', 'Product');
        
        if ($historyType == 'Product') {
            $mappingSettings = $this->helperData->getMappingSettings();
            $mapping = [
                'page title' => $mappingSettings['page_title'],
                'page description' => $mappingSettings['page_description'],
                'product description' => $mappingSettings['product_description'],
                'excerpt' => $mappingSettings['short_product_description']
            ];
            if (isset($mappingSettings['open_graph'])) {
                $mapping['open graph text'] = $mappingSettings['open_graph'];
            }
        } else {
            $mappingSettings = $this->categoriesFieldsHelper->getMappingSettings();
            $mapping = [
                Fields::CATEGORY_PAGE_TITLE => $mappingSettings['page_title'],
                Fields::CATEGORY_PAGE_DESCRIPTION => $mappingSettings['page_description'],
                Fields::CATEGORY_DESCRIPTION => $mappingSettings['category_description']
            ];
        }

        foreach ($history as $entry) {
            if (!isset($mapping[$entry['textType']])) {
                continue;
            }

            $value = $this->getRecordDataValue($entry, $historyType, $storeId, $mapping);
            if ($value === 'entity not found') {
                continue;
            }
            $date = $this->timezone->date($entry['timestamp']);
            $requestId = $entry['requestId'] ?? '';
            $groupKey = $requestId . '.' . $entry['action'] . '.' . $entry['recordId'];

            if (!isset($entries[$groupKey])) {
                $userName = $this->getUserNameByEmail($entry['editor']);
                $entry['actionDisplay'] = $this->replaceLabel($entry['actionDisplay'], $entry['action']);
                $entries[$groupKey] = [
                    'timestamp' => $date->format('M d, Y') . ' | ' . $date->format('H:i'),
                    'timestamp_sort' => $date->getTimestamp(),
                    'actionDisplay' => $userName ? str_replace(
                        $entry['editor'],
                        $userName,
                        $entry['actionDisplay']
                    ) : $entry['actionDisplay'],
                    'recordName' => $this->entities[$entry['recordId']]['name'] ?? '',
                    'recordId' => $entry['recordId'],
                    'storeId' => $storeId,
                    'requestId' => $requestId,
                    'action' => $entry['action'],
                    'textEntries' => []
                ];
            } else {
                $entryTimestamp = $date->getTimestamp();
                if ($entryTimestamp > ($entries[$groupKey]['timestamp_sort'] ?? 0)) {
                    $entries[$groupKey]['timestamp_sort'] = $entryTimestamp;
                    $entries[$groupKey]['timestamp'] = $date->format('M d, Y') . ' | ' . $date->format('H:i');
                }
            }

            if (!isset($entries[$groupKey]['textEntries'][$entry['textType']])) {
                $entries[$groupKey]['textEntries'][$entry['textType']] = [];
            }
            
            $entries[$groupKey]['textEntries'][$entry['textType']][] = $value;
        }

        $textTypeOrder = [];
        if ($historyType == 'Product') {
            $textTypeOrder = [
                'page title',
                'page description',
                'product description',
                'excerpt',
            ];
            if (isset($mappingSettings['open_graph'])) {
                $textTypeOrder[] = 'open graph text';
            }
        } else {
            $textTypeOrder = [
                Fields::CATEGORY_PAGE_TITLE,
                Fields::CATEGORY_PAGE_DESCRIPTION,
                Fields::CATEGORY_DESCRIPTION,
            ];
        }

        foreach ($entries as $key => $entry) {
            if (!isset($entry['textEntries']) || !is_array($entry['textEntries'])) {
                continue;
            }

            $sortedTextEntries = [];
            foreach ($textTypeOrder as $textType) {
                if (isset($entry['textEntries'][$textType])) {
                    $sortedTextEntries[$textType] = $entry['textEntries'][$textType];
                }
            }

            foreach ($entry['textEntries'] as $textType => $values) {
                if (isset($sortedTextEntries[$textType])) {
                    continue;
                }
                $sortedTextEntries[$textType] = $values;
            }

            $entries[$key]['textEntries'] = $sortedTextEntries;
        }

        uasort($entries, function ($a, $b) {
            $aTime = (int) ($a['timestamp_sort'] ?? 0);
            $bTime = (int) ($b['timestamp_sort'] ?? 0);
            if ($aTime !== $bTime) {
                return $bTime <=> $aTime;
            }

            $aReq = (string) ($a['requestId'] ?? '');
            $bReq = (string) ($b['requestId'] ?? '');
            $aRec = (string) ($a['recordId'] ?? '');
            $bRec = (string) ($b['recordId'] ?? '');

            if ($aReq === $bReq && $aRec === $bRec) {
                $priority = [
                    'Transferred,Magento' => 10,
                    'Transferred' => 20,
                    'Reviewed' => 30
                ];
                $aAction = (string) ($a['action'] ?? '');
                $bAction = (string) ($b['action'] ?? '');
                $aPrio = $priority[$aAction] ?? 999;
                $bPrio = $priority[$bAction] ?? 999;
                if ($aPrio !== $bPrio) {
                    return $aPrio <=> $bPrio;
                }

                return $aAction <=> $bAction;
            }

            return $bReq <=> $aReq;
        });
        foreach ($entries as $key => $entry) {
            unset($entries[$key]['timestamp_sort']);
        }

        return $entries;
    }

    /**
     * Get params for API request
     *
     * @return mixed[]
     */
    private function getParams()
    {
        $params = $this->getRequest()->getParams();
        $data = [
            'type' => $params['history_type'],
            'storeId' => $params['store_id'],
            'recordId' => $params['record_id'],
        ];

        $language = $this->storeHelper->getFormattedLanguage($params['store_id']);
        $data['language'] = $language;

        if (isset($params['start_date']) && $params['start_date']) {
            $startDate = $this->timezone->convertConfigTimeToUtc($params['start_date'] . ' 00:00:00');
            $data['startDate'] = $startDate;
        }

        if (isset($params['end_date']) && $params['end_date']) {
            $endDate = $this->timezone->convertConfigTimeToUtc($params['end_date'] . ' 23:59:59');
            $data['endDate'] = $endDate;
        }

        if (isset($params['user']) && $params['user']) {
            $model = $this->userFactory->create();
            $user = $model->load($params['user']);

            if ($user->getId()) {
                $data['userName'] = $user->getEmail();
            }
        }

        if (isset($params['token']) && $params['token']) {
            $data['continuationToken'] = $params['token'];
        }
        
        return $data;
    }

    /**
     * Get user name by email
     *
     * @param string $email
     * @return string
     */
    protected function getUserNameByEmail($email)
    {
        try {
            $users = $this->getUsers();

            foreach ($users as $user) {
                if ($user->getEmail() == $email) {
                    return $user->getFirstname() . ' ' . $user->getLastname();
                }
            }
        } catch (\Exception $e) {
            return '';
        }
    }

    /**
     * Get users
     *
     * @return \Magento\User\Model\ResourceModel\User\Collection
     */
    protected function getUsers()
    {
        if (!$this->users) {
            $collection = $this->userCollectionFactory->create();
            $this->users = $collection;
        }

        return $this->users;
    }

    /**
     * Collect products
     *
     * @param array $history
     * @return void
     */
    protected function collectProducts($history)
    {
        $storeId = $this->getRequest()->getParam('store_id', 0);
        
        try {
            $productIds = array_unique(array_column($history, 'recordId'));
        } catch (\Exception $e) {
            return;
        }

        $collection = $this->productCollectionFactory->create();
        $collection->setStoreId($storeId);
        $collection->addAttributeToSelect('name');
        $collection->addFieldToFilter('entity_id', ['in' => $productIds]);

        foreach ($collection as $product) {
            if (!isset($this->entities[$product->getId()])) {
                $productName = $this->outputHelper->productAttribute(
                    $product,
                    $product->getName(),
                    'name'
                );
                $this->entities[$product->getId()] = [
                    'name' => $productName,
                    'entity' => $product
                ];
            }
        }
    }

    /**
     * Collect categories
     *
     * @param array $history
     * @return void
     */
    protected function collectCategories($history)
    {

        $storeId = $this->getRequest()->getParam('store_id', 0);
        
        try {
            $categoryIds = array_unique(array_column($history, 'recordId'));
        } catch (\Exception $e) {
            return;
        }

        $collection = $this->categoryCollectionFactory->create();
        $collection->setStoreId($storeId);
        $collection->addAttributeToSelect('name');
        $collection->addFieldToFilter('entity_id', ['in' => $categoryIds]);

        foreach ($collection as $category) {
            if (!isset($this->entities[$category->getId()])) {
                $categoryName = $this->outputHelper->categoryAttribute(
                    $category,
                    $category->getName(),
                    'name'
                );
                $this->entities[$category->getId()] = [
                    'name' => $categoryName,
                    'entity' => $category
                ];
            }
        }
    }

    /**
     * Get record data value
     *
     * @param array $entry
     * @param string $type
     * @param int $storeId
     * @param array $mapping
     * @return string
     */
    protected function getRecordDataValue($entry, $type, $storeId, $mapping)
    {
        $value = '';
        $entity = $this->entities[$entry['recordId']]['entity'] ?? null;
        $methodAttribute = '';
        if (!$entity) {
            try {
                if ($type == 'Product') {
                    $entity = $this->productRepository->getById(
                        $entry['recordId'],
                        false,
                        $storeId
                    );
                } else {
                    $entity = $this->categoryRepository->get(
                        $entry['recordId'],
                        $storeId
                    );
                }
            } catch (NoSuchEntityException $e) {
                return 'entity not found';
            }
        }
        
        $methodAttribute = $type === 'Product' ? 'productAttribute' : 'categoryAttribute';
        $value = $this->outputHelper->$methodAttribute(
            $entity,
            $entry['value'],
            $mapping[$entry['textType']]
        );
        return $value;
    }

    /**
     * Replace label with value
     *
     * @param string $initialValue
     * @param string $action
     * @return string
     */
    public function replaceLabel($initialValue, $action)
    {
        $returnValue = $initialValue;
        switch ($action) {
            case 'ForTransferring':
                $returnValue = str_replace(
                    'Tagged for transferring',
                    'Queued for transfer to Magento',
                    $initialValue
                );
                break;
            default:
                # code...
                break;
        }
        
        return $returnValue;
    }
}
