<?php

namespace FiloBlu\Flow\Model\Channel\In;

use Exception;
use FiloBlu\Flow\Api\SwatchInterfaceFactory;
use FiloBlu\Flow\Helper\LoggerProvider;
use FiloBlu\Flow\Model\Channel\ConfigFactory;
use FiloBlu\Flow\Model\Manager\ProductImageManager;
use Magento\Eav\Model\Config;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\WriteInterface;
use Magento\Framework\ObjectManagerInterface;
use Magento\MediaStorage\Model\File\Uploader;
use Magento\Swatches\Helper\Data;
use Zend_Db_Statement_Exception;

use function in_array;

/**
 * Class Swatch
 * @package FiloBlu\Flow\Model\Channel\In
 */
class Swatch extends AbstractModel
{
    /**
     * @var string
     */
    const STATUS_RECEIVED = 'received';

    /**
     * @var string
     */
    const STATUS_PROCESSED = 'processed';

    /**
     * @var string
     */
    const STATUS_PARSED = 'parsed';

    /**
     * @var string
     */
    const STATUS_ERROR = 'error';

    /**
     * @var ResourceConnection
     */
    protected $resourceConnection;

    /**
     * @var AdapterInterface
     */
    protected $connection;

    /**
     * @var WriteInterface
     */
    protected $mediaDirectory;

    /**
     * @var WriteInterface
     */
    protected $varDirectory;

    /**
     * @var
     */
    protected $channel_photo_config;

    /**
     * @var
     */
    protected $collection_of_images_to_parse;

    /**
     * @var
     */
    protected $array_of_data_for_csv;

    /**
     * @var string
     */
    protected $filename_separator = '_';

    /**
     * @var int
     */
    protected $_errorsProcessingImages = 0;

    /**
     * @var int
     */
    protected $_imagesToProcess = 0;

    /**
     * @var string[]
     */
    protected $allowed_extensions = ['jpg', 'jpeg', 'gif', 'png'];
    /**
     * @var SwatchInterfaceFactory
     */
    private $swatchFactory;
    /**
     * @var Data
     */
    private $swatchHelper;
    /**
     * @var Config
     */
    private $eavConfig;

    /**
     * Swatch constructor.
     * @param LoggerProvider $loggerProvider
     * @param ConfigFactory $channelConfigFactory
     * @param ResourceConnection $resourceConnection
     * @param ObjectManagerInterface $objectManager
     * @param SwatchInterfaceFactory $swatchFactory
     * @param Config $eavConfig
     * @param Data $swatchHelper
     * @param \Magento\Framework\Filesystem $filesystem
     * @throws \Magento\Framework\Exception\FileSystemException
     */
    public function __construct(
        LoggerProvider         $loggerProvider,
        ConfigFactory          $channelConfigFactory,
        ResourceConnection     $resourceConnection,
        ObjectManagerInterface $objectManager,
        SwatchInterfaceFactory $swatchFactory,
        Config                 $eavConfig,
        Data                   $swatchHelper,
        Filesystem             $filesystem
    )
    {
        parent::__construct($loggerProvider, $channelConfigFactory, $objectManager);

        $this->resourceConnection = $resourceConnection;

        $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
        $this->varDirectory = $filesystem->getDirectoryWrite(DirectoryList::VAR_DIR);

        $this->eavConfig = $eavConfig;
        $this->swatchHelper = $swatchHelper;

        $this->swatchFactory = $swatchFactory;
    }

    /**
     * @return int
     */
    public function getProcessedImageErrorCount()
    {
        return $this->_errorsProcessingImages;
    }

    /**
     * @return int
     */
    public function getImageToProcessCount()
    {
        return $this->_imagesToProcess;
    }

    /**
     * @param $file
     * @return bool
     * @throws Zend_Db_Statement_Exception
     */
    public function insertData($file)
    {
        $meta_file_id = $file->getId();

        $model = $this->swatchFactory->create();

        // Loading images SKUs to process
        $collection = $model->getCollection();
        $collection->addFieldToSelect(['id', 'name', 'attribute', 'option_name', 'option_id', 'type', 'path']);
        $collection->addFieldToFilter('meta_processed', 0);
        $collection->addFieldToFilter('meta_file', $meta_file_id);
        $collection->addFieldToFilter('status', ['in' => [self::STATUS_PARSED]]);
        $collection->load();

        $this->_imagesToProcess = $collection->count();

        foreach ($collection->getItems() as $item) {
            $this->processSwatch($item);
        }

        return !($this->_errorsProcessingImages > 0);
    }

    /**
     * @param $swatchItem
     * @return bool
     */
    public function processSwatch($swatchItem)
    {

        // TODO : find best place
        /** @var ProductImageManager $imageManager */
        $imageManager = $this->objectManager->get(ProductImageManager::class);

        /* Where Magento 2 looks for images */
        $mediaRootDirectory = $this->mediaDirectory->getAbsolutePath();
        $swatchRelativePath = 'attribute' . DIRECTORY_SEPARATOR . 'swatch';

        try {
            // Normalize Image Name
            $imageToMove = strtolower(preg_replace('![^a-z0-9.]+!i', '_', $swatchItem->getName()));
            $relativePath = Uploader::getDispretionPath($imageToMove) . DIRECTORY_SEPARATOR . $imageToMove;
            $to = $mediaRootDirectory . $swatchRelativePath . $relativePath;
            $from = $this->getImportImagePath($swatchItem);

            $folder = dirname($to) . DIRECTORY_SEPARATOR;

            if (is_dir($folder) === false) {
                if (mkdir($folder, 0777, true) || is_dir($folder)) {
                    $this->_logger->error("Unable to create directory {$folder}");
                    $this->setProcecessedStatus($swatchItem, self::STATUS_ERROR, "Unable to create directory {$folder}");
                    $this->_errorsProcessingImages++;
                    return true;
                }
            }

            if (copy($from, $to) === false) {
                $this->setProcecessedStatus($swatchItem, self::STATUS_ERROR, "Unable to move {$from} -> {$to}");
                $this->_errorsProcessingImages++;
                return false;
            }

            $optionSwatchTable = $this->getConnection()->getTableName('eav_attribute_option_swatch');

            $sql = "INSERT INTO {$optionSwatchTable}
                    (`option_id`, `store_id`, `type`, `value`)
                    VALUES
                    ({$swatchItem->getOptionId()}, 0, '{$swatchItem->getType()}', '{$relativePath}')
                    ON DUPLICATE KEY UPDATE
                    `type` = '{$swatchItem->getType()}',
                    `value` = '{$relativePath}'";

            $this->getConnection()->query(
                $sql
            );

            //clean swatch cache?
            /*try {
                $imageManager->cleanCache($swatchItem->getName());
            } catch (Throwable $throwable) {
                $this->_logger->error($throwable->getMessage());
            }*/

            $this->setProcecessedStatus($swatchItem, self::STATUS_PROCESSED);
        } catch (Exception $e) {
            $this->_logger->info("Importing image {$swatchItem->getName()} for attribute {$swatchItem->getAttribute()} with name {$swatchItem->getOptionName()} failed. : " . $e->getMessage() . PHP_EOL . $e->getTraceAsString());
            $this->setProcecessedStatus($swatchItem, self::STATUS_ERROR, $e->getMessage());
            $this->_errorsProcessingImages++;
        }

        return true;
    }

    /**
     * @param \FiloBlu\Flow\Model\Swatch $swatch
     * @return string
     */
    protected function getImportImagePath(\FiloBlu\Flow\Model\Swatch $swatch)
    {
        return $this->varDirectory->getAbsolutePath() . DIRECTORY_SEPARATOR . $swatch->getPath() . DIRECTORY_SEPARATOR . $swatch->getName();
    }

    /**
     * @param \FiloBlu\Flow\Model\Swatch $swatch
     * @param string $status
     * @param null $reason
     */
    protected function setProcecessedStatus(\FiloBlu\Flow\Model\Swatch $swatch, $status = self::STATUS_PROCESSED, $reason = null)
    {
        $connection = $this->getConnection();
        $connection->update(
            $connection->getTableName('flow_swatch'),
            [
                'meta_processed' => 1,
                'status'         => $status,
                'log'            => $reason
            ],
            $connection->quoteInto('id = ?', $swatch->getId())
        );
    }

    /**
     * @return AdapterInterface
     */
    protected function getConnection()
    {
        if ($this->connection) {
            return $this->connection;
        }
        return ($this->connection = $this->resourceConnection->getConnection());
    }

    /**
     * @param $file
     * @return bool
     * @throws Exception
     */
    public function receiveFile($file)
    {
        if (!$this->_connector) {
            throw new Exception('no connector joined to channel');
        }

        $meta_file_id = $file->getId();

        $local_dir = $this->_connector->getLocalDir() . '/' . $meta_file_id;

        $this->_connector->setLocalDir($local_dir);

        $model = $this->swatchFactory->create();

        $collection = $model->getCollection();
        $collection->addFieldToFilter('meta_processed', 0);
        $collection->addFieldToFilter('meta_file', $meta_file_id);
        $collection->addFieldToFilter('status', 'found');
        $collection->load();

        $result = true;

        foreach ($collection as $image) {
            try {
                $unique_filename_pattern = false;

                $received = $this->_connector->receiveResource($image->getData('name'), null, FTP_BINARY, true, $unique_filename_pattern);
                if ($received) {
                    $image->setData('status', self::STATUS_RECEIVED);
                    if ($image->getData('name') !== $received) {
                        $log = $image->getData('log');
                        $log = trim("Image name fixed from '" . $image->getData('name') . "' to '{$received}'" . $log . "\n");
                        $image->setData('log', $log);
                        $image->setData('name', $received);
                    }
                    $image->save();
                } else {
                    $image->setData('status', 'found');
                    $log = $image->getData('log');
                    $log .= "Image not received\n";
                    $image->setData('log', $log);
                    $image->save();
                    $result = false;
                }
            } catch (Exception $e) {
                $log = $image->getData('log');
                $log .= $e->getMessage() . "\n";
                $image->setData('log', $log);
                $image->save();
                $result = false;
            }
        }

        return $result;
    }

    /**
     * @param $file
     * @return bool
     * @throws Exception
     */
    public function parse($file)
    {
        if (!$this->_connector) {
            throw new Exception('no connector joined to channel');
        }

        $meta_file_id = $file->getId();

        $channel_config = $this->_connector->getConfig();
        $local_dir = $channel_config['local_dir'] . DIRECTORY_SEPARATOR . $meta_file_id;

        $channel_name = $file->getChannel();
        $this->loadImageChannelConfig($channel_name);
        $this->filename_separator = $this->getImageSeparator();

        $this->loadImagesListToParse($meta_file_id);

        foreach ($this->collection_of_images_to_parse as $data) {
            $image_filename = $data->getName();

            $image_extension = pathinfo($image_filename, PATHINFO_EXTENSION);

            //check if is an image
            if (!in_array(strtolower($image_extension), $this->allowed_extensions)) {
                $data->setData('status', 'error');
                $data->setData('log', $image_extension . ' is not an allowed extension.');
                $data->save();
                $log = $file->getData('log');
                $log .= $image_extension . " is not an allowed extension.\n";
                $file->setData('log', $log);
                $file->save();
                continue;
            }

            $loaded_data = $this->loadDataBasedOnFilename($image_filename, $channel_name);

            //check if exists attribute and if is swatch
            if (!$this->isSwatchAttr($loaded_data['attribute'])) {
                $data->setData('status', 'error');
                $data->setData('log', $loaded_data['attribute'] . ' is not a swatch attribute or doesn\'t exists.');
                $data->save();
                $log = $file->getData('log');
                $log .= $loaded_data['attribute'] . " is not a swatch attribute or doesn\'t exists.\n";
                $file->setData('log', $log);
                $file->save();
                continue;
            }

            // Get the option id by option name for attribute
            $option_id = $this->getAttributeOptionId($loaded_data['option_name'], $loaded_data['attribute']);

            // If attribute exists and is a swatch and if the value is matched
            if (!empty($option_id)) {
                $data->setData('attribute', $loaded_data['attribute']);
                $data->setData('option_name', $loaded_data['option_name']);
                $data->setData('option_id', $option_id);
                $data->setData('type', $loaded_data['type']);
                $data->setData('path', $local_dir);
                $data->setData('status', 'parsed');
                $data->save();
            } else {
                $data->setData('status', 'error');
                $data->setData('log', $loaded_data['option_name'] . " doesn't exists in " . $loaded_data['attribute'] . ' attribute');
                $data->save();
                $log = $file->getData('log');
                $log .= $loaded_data['option_name'] . " doesn't exists in " . $loaded_data['attribute'] . " attribute\n";
                $file->setData('log', $log);
                $file->save();
                continue;
            }
        }

        return true;
    }

    /**
     * @param $channel_name
     */
    protected function loadImageChannelConfig($channel_name)
    {
        $this->channel_photo_config = null;
        if ($this->_parser->loadMap($channel_name)) {
            $config_mapping = $this->_parser->map->getFullMappedData();
            $this->channel_photo_config = $config_mapping;
        }
    }

    /**
     * @return string
     */
    protected function getImageSeparator()
    {
        $config = $this->channel_photo_config;

        $has_separator = (isset($config['options']['separator']) && trim($config['options']['separator']) !== '');
        return $has_separator ? $config['options']['separator'] : '_';
    }

    /**
     * @param $file_id
     */
    protected function loadImagesListToParse($file_id)
    {
        $model = $this->swatchFactory->create();

        $collection = $model->getCollection();
        $collection->addFieldToFilter('meta_processed', 0);
        $collection->addFieldToFilter('meta_file', $file_id);
        $collection->addFieldToFilter('status', 'received');
        $collection->load();

        $this->collection_of_images_to_parse = $collection;
    }

    /**
     * @param $image_filename
     * @param null $channel_name
     * @return mixed
     */
    protected function loadDataBasedOnFilename($image_filename, $channel_name = null)
    {
        $alt = [];
        $original_filename = $image_filename;

        // Remove extension
        $image_filename = pathinfo($original_filename, PATHINFO_FILENAME);

        // Explode filename
        $filename_exploded = explode($this->filename_separator, $image_filename);

        $attribute = $filename_exploded[0] ?? '_unknown_';

        $attribute_name = $filename_exploded[1] ?? '_unknown_';

        $type = 2; // image swatch

        $this->array_of_data_for_csv[$attribute][$attribute_name]['filename'] = $original_filename;
        $this->array_of_data_for_csv[$attribute][$attribute_name]['attribute'] = $attribute;
        $this->array_of_data_for_csv[$attribute][$attribute_name]['path'] = $this->_connector->getLocalDir();
        $this->array_of_data_for_csv[$attribute][$attribute_name]['type'] = $type;
        $this->array_of_data_for_csv[$attribute][$attribute_name]['option_name'] = $attribute_name;

        return $this->array_of_data_for_csv[$attribute][$attribute_name];
    }

    /**
     * @return bool
     * @throws LocalizedException
     */
    public function isSwatchAttr($attributeCode, $entityType = 'catalog_product')
    {
        try {
            $attribute = $this->eavConfig->getAttribute($entityType, $attributeCode);
            return $this->swatchHelper->isSwatchAttribute($attribute);
        } catch (NoSuchEntityException $e) {
            return false;
        }
    }

    /**
     * @param $value
     * @param $attributeCode
     * @param string $entityType
     * @return mixed
     * @throws LocalizedException
     */
    public function getAttributeOptionId($value, $attributeCode, $entityType = 'catalog_product')
    {
        $attribute = $this->eavConfig->getAttribute($entityType, $attributeCode);
        return $attribute->setStoreId(0)->getSource()->getOptionId($value);
    }

    /**
     * @param $string
     * @return bool|string
     */
    public function removeExtensionFromFilename($string)
    {
        $p = strrpos($string, '.');
        if ($p !== false) {
            $string = substr($string, 0, $p);
        }

        return $string;
    }
}
