<?php
/**
 * @author Aitoc Team
 * @copyright Copyright (c) 2020 Aitoc (https://www.aitoc.com)
 * @package Aitoc_PreOrders
 */

/**
 * Copyright © 2018 Aitoc. All rights reserved.
 * Developed by Alex Yakimovich
 */

namespace Aitoc\PreOrders\Model;

use Aitoc\PreOrders\Api\AttributesManagementInterface;
use Aitoc\PreOrders\Api\Data\ProductAttributesInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Module\Dir\Reader as ModuleDirReader;
use Magento\Framework\Xml\Parser as XmlParser;
use Magento\Framework\Filesystem\Driver\File as FileReader;
use Magento\Store\Model\StoreManagerInterface;

class AttributesManagement implements AttributesManagementInterface
{
    const XML_FILE_NAME = 'product_attributes.xml';

    /**
     * @var ProductAttributesRepository
     */
    private $productAttributesRepository;

    /**
     * @var ProductAttributesFactory
     */
    private $productAttributesFactory;

    /**
     * @var ModuleDirReader
     */
    private $moduleDirReader;

    /**
     * Filesystem driver to allow reading of module.xml files which live outside of app/code
     *
     * @var FileReader
     */
    private $fileReader;

    /**
     * @var XmlParser
     */
    private $xmlParser;

    /**
     * @var array[]
     */
    private $attributes;

    /**
     * @var StoreManagerInterface
     */
    private $storeManager;

    /**
     * @param ProductAttributesRepository $productAttributesRepository
     * @param ProductAttributesFactory $productAttributesFactory
     * @param ModuleDirReader $moduleDirReader
     * @param XmlParser $xmlParser
     * @param FileReader $fileReader
     * @param StoreManagerInterface $storeManager
     * @throws FileSystemException
     * @throws LocalizedException
     */
    public function __construct(
        ProductAttributesRepository $productAttributesRepository,
        ProductAttributesFactory $productAttributesFactory,
        ModuleDirReader $moduleDirReader,
        XmlParser $xmlParser,
        FileReader $fileReader,
        StoreManagerInterface $storeManager
    ) {
        $this->productAttributesRepository = $productAttributesRepository;
        $this->productAttributesFactory = $productAttributesFactory;
        $this->moduleDirReader = $moduleDirReader;
        $this->xmlParser = $xmlParser;
        $this->fileReader = $fileReader;
        $this->storeManager = $storeManager;
        $this->initAttributesInfo();
    }

    /**
     * @param int $productId
     * @param string $attributeCode
     * @param int $storeId
     * @return string|null
     * @throws LocalizedException
     */
    public function getAttributeValue($productId, $attributeCode, $storeId = 0, $recursively = true)
    {
        if (!array_key_exists($attributeCode, $this->attributes)) {
            throw new LocalizedException(__('Attribute does not exist'));
        }

        $attributeModel = $this->getAttributeModel($productId, $attributeCode, $storeId, $recursively);

        return $this->prepareAttributeValue($attributeModel);
    }

    /**
     * @param ProductAttributesInterface $attributeModel
     * @return bool|int|string
     * @throws LocalizedException
     */
    private function prepareAttributeValue(ProductAttributesInterface $attributeModel)
    {
        $attributeType = $this->attributes[$attributeModel->getAttributeCode()]['type'];
        $attributeValue = $attributeModel->getValue();
        switch ($attributeType) {
            case 'int' :
                return is_numeric($attributeValue) ? (int)$attributeValue : $attributeValue;
                break;

            case 'string' :
                return (string)$attributeValue;
                break;

            case 'bool' :
                return (bool)$attributeValue;
                break;

            default :
                throw new LocalizedException(_('Invalid attribute type'));
                break;
        }
    }

    /**
     * @param int $productId
     * @param int $storeId
     * @return array|mixed
     * @throws LocalizedException
     */
    public function getAttributesByProduct($productId, $storeId = 0)
    {
        if ($storeId == 0) {
            $storeId = $this->storeManager->getStore()->getId();
        }
        $attributes = [];
        foreach ($this->getAttributesList() as $attributeCode) {
            $attributes[$attributeCode] = $this->getAttributeValue($productId, $attributeCode, $storeId);
        }
        return $attributes;
    }

    /**
     * Retrieve list of attributes
     * @return array
     */
    public function getAttributesList()
    {
        return array_keys($this->attributes);
    }

    /**
     * @param int $productId
     * @param string $attributeCode
     * @param mixed $value
     * @param int $storeId
     * @throws LocalizedException
     */
    public function saveAttributeValue($productId, $attributeCode, $value, $storeId = 0)
    {
        if (in_array($attributeCode, $this->getAttributesList())) {
            $attributeModel = $this->getAttributeModel($productId, $attributeCode, $storeId);
            $attributeModel->setProductId($productId)
                ->setAttributeCode($attributeCode)
                ->setStoreId($storeId)
                ->setValue($value);
            $this->productAttributesRepository->save($attributeModel);
        } else {
            throw new LocalizedException(__('Attribute does not exist'));
        }
    }

    /**
     * @param int $productId
     * @param string $attributeCode
     * @param int $storeId
     * @throws LocalizedException
     */
    public function removeAttributeValue($productId, $attributeCode, $storeId = 0)
    {
        if (in_array($attributeCode, $this->getAttributesList())) {
            $attributeModel = $this->getAttributeModel($productId, $attributeCode, $storeId);
            $this->productAttributesRepository->delete($attributeModel);
        } else {
            throw new LocalizedException(__('Attribute does not exist'));
        }
    }

    /**
     * @throws FileSystemException
     * @throws LocalizedException
     */
    private function initAttributesInfo()
    {
        $this->readXmlFile();
        $this->loadAttributes();
    }

    /**
     * @throws LocalizedException
     * @throws FileSystemException
     */
    private function readXmlFile()
    {
        $etcFolderPath = $this->moduleDirReader->getModuleDir('etc', 'Aitoc_PreOrders');
        $xmlPath = $etcFolderPath . '/' . self::XML_FILE_NAME;
        $xmlContent = $this->fileReader->fileGetContents($xmlPath);

        try {
            $this->xmlParser->loadXML($xmlContent);
        } catch (LocalizedException $e) {
            throw new LocalizedException(
                new \Magento\Framework\Phrase(
                    'Invalid Document: %1%2 Error: %3',
                    [$xmlPath, PHP_EOL, $e->getMessage()]
                ),
                $e
            );
        }
    }

    private function getAttributeModel($productId, $attributeCode, $storeId = 0, $recursively = false)
    {
        $model = $this->productAttributesRepository->getByProductId(
            $productId,
            $attributeCode,
            $storeId
        );

        if ($model === false) {
            if ($recursively == true && $storeId != 0) {
                $model = $this->getAttributeModel($productId, $attributeCode, 0);
            } else {
                $model = $this->productAttributesFactory->create();
                $model->setProductId($productId)
                    ->setAttributeCode($attributeCode)
                    ->setStoreId($storeId);
            }
        }

        return $model;
    }

    /**
     * Read xml file with attributes
     */
    private function loadAttributes()
    {
        $source = $this->xmlParser->getDom();
        $xpath = new \DOMXPath($source);
        /** @var $moduleNode \DOMNode */
        foreach ($xpath->query('/config/product_attributes') as $attributesNode) {
            $attributesData = [];
            foreach ($attributesNode->childNodes as $childNode) {
                switch ($childNode->nodeName) {
                    case 'attribute':
                        $code = $childNode->getAttribute('code');
                        $attributesData[$code] = [
                            'type' => $childNode->getAttribute('type'),
                            'label' => $childNode->textContent
                        ];
                        break;
                }
            }
        }
        $this->attributes = $attributesData;
    }

    /**
     * @param $productId
     * @param $attributeCode
     * @param int $storeId
     * @return bool
     * @throws LocalizedException
     */
    public function isUsedDefault($productId, $attributeCode, $storeId = 0)
    {
        if (!array_key_exists($attributeCode, $this->attributes)) {
            throw new LocalizedException(__('Attribute does not exist'));
        }

        $attributeModel = $this->getAttributeModel($productId, $attributeCode, $storeId, true);

        if ($storeId != 0 && $attributeModel->getStoreId() != $storeId) {
            return true;
        }

        return false;
    }
}