<?php

namespace Webkul\WebkulCsvImportBundle\Connector\Writer;

use Doctrine\ORM\EntityManager;
use Symfony\Component\Filesystem\Filesystem;
use Webkul\WebkulCsvImportBundle\Classes\ShopifyClass;
use Webkul\WebkulCsvImportBundle\Services\WebkulCsvConnector;
use Webkul\WebkulCsvImportBundle\Connector\Processor\ProductCsvProcessor;

$classLoader = new \Webkul\WebkulCsvImportBundle\Listener\ClassDefinationForCompatibility();
$classLoader->customLoaderSystem();

class ProductCsvWriter extends \AbstractItemMediaWriter implements
    \ItemWriterInterface,
    \InitializableInterface,
    \FlushableInterface,
    \StepExecutionAwareInterface
{
    /** @var \ArrayConverterInterface */
    protected $arrayConverter;

    /** @var \FlatItemBufferFlusher */
    protected $flusher;

    /** @var \BufferFactory */
    protected $bufferFactory;

    /** @var \FlatItemBuffer */
    protected $flatRowBuffer;

    /** @var \AttributeRepositoryInterface */
    protected $attributeRepository;

    /** @var \FileExporterPathGeneratorInterface */
    protected $fileExporterPath;

    /** @var string[] */
    protected $mediaAttributeTypes;

    /** @var ConnectorService */
    protected $connectorService;

    /** @var Filesystem */
    protected $localFs;

    /** @var \StepExecution */
    protected $stepExecution;
    
    /** @var \AttributeConverterInterface */
    protected $localizer;
    
    /** @var array */
    protected $variantAxesField = [];

    /** @var string */
    protected $storeTmpName;

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

    /** @var bool */
    protected $withHeader = false;

    /** @var \JobParameters $parameters */
    protected $parameters;

    /** @var string DateTime format for the file path placeholder */
    protected $datetimeFormat = 'Y-m-d_H-i-s';

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

    /** @var bool */
    protected $headerMerge = false;
    
    /** @var ProductCsvProcessor */
    protected $productCsvProcessor;

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

    /** @var bool */
    protected $exportVariantAfterModel;

    /** @var bool */
    protected $productExportStarted = false;

    /** @var int */
    protected $variantProductCount = 0;
    
    /**
     * @param \ArrayConverterInterface            $arrayConverter
     * @param \BufferFactory                      $bufferFactory
     * @param \FlatItemBufferFlusher              $flusher
     * @param \AttributeRepositoryInterface       $attributeRepository
     * @param \FileExporterPathGeneratorInterface $fileExporterPath
     * @param \AttributeConverterInterface        $localizer
     * @param array                               $mediaAttributeTypes
     * @param WebkulCsvConnector                  $connectorService
     */
    public function __construct(
        \ArrayConverterInterface $arrayConverter,
        \BufferFactory $bufferFactory,
        \FlatItemBufferFlusher $flusher,
        \AttributeRepositoryInterface $attributeRepository,
        \FileExporterPathGeneratorInterface $fileExporterPath,
        \AttributeConverterInterface $localizer,
        array $mediaAttributeTypes,
        WebkulCsvConnector $connectorService,
        ProductCsvProcessor $productCsvProcessor
    ) {
        $this->arrayConverter = $arrayConverter;
        $this->bufferFactory = $bufferFactory;
        $this->flusher = $flusher;
        $this->attributeRepository = $attributeRepository;
        $this->fileExporterPath = $fileExporterPath;
        $this->mediaAttributeTypes = $mediaAttributeTypes;
        $this->localizer = $localizer;
        $this->localFs = new Filesystem();
        $this->connectorService = $connectorService;
        $this->productCsvProcessor = $productCsvProcessor;
    }
    
    /**
      * {@inheritdoc}
      */
    public function initialize()
    {
        if (null === $this->flatRowBuffer) {
            $this->flatRowBuffer = $this->bufferFactory->create();
        }

        $this->parameters = $this->stepExecution->getJobParameters();
        if (empty($this->parameters->get('exportProfile'))) {
            throw new \Exception(sprintf("Please select template store first"));
        }
        $this->exportVariantAfterModel = $this->parameters->get('exportVariantAfterModel');
        $templateStore = $this->connectorService->getTemplateStore($this->parameters->get('exportProfile'));
        $storeTemplateData       = json_decode($templateStore->getRawValues(), true);
        $storeTemplateData       = isset($storeTemplateData['csvExportMapping']) ? $storeTemplateData['csvExportMapping'] : [];
        $this->storeTemplateData = isset($storeTemplateData['rawValues']) ? $storeTemplateData['rawValues']: [];

        if (empty($templateStore) || $templateStore == null) {
            throw new \Exception(sprintf("Template mapping is empty"));
        }
        $this->variantAxesField = isset($storeTemplateData['variantAxesField']) ? $storeTemplateData['variantAxesField'] : [];
        $extraMapping           = isset($storeTemplateData['extraMapping']) ? $storeTemplateData['extraMapping'] : [];
        $this->storeTmpName     = 'wk_akeneo_'. strtolower($templateStore->getName()) .'_csv_export';
        $this->setConverterOptions($this->parameters);
        $exportDirectory = dirname($this->getPath());
        if (!is_dir($exportDirectory)) {
            $this->localFs->mkdir($exportDirectory);
        }
        if ($this->parameters->has('withHeader')
            && $this->parameters->get('withHeader')
        ) {
            $this->withHeader = true;
        }
        
        $this->headers = !empty($this->storeTemplateData) ? array_keys($this->storeTemplateData) : [];
        if (!empty($extraMapping['enabledFieldName'])) {
            if ($pos = array_search($extraMapping['enabledFieldName'], $this->headers)) {
                unset($this->headers[$pos]);
            }
            $this->headers['enabledFieldName'] = $extraMapping['enabledFieldName'];
        }
        if (!empty($extraMapping['categoryFieldName'])) {
            if ($pos = array_search($extraMapping['categoryFieldName'], $this->headers)) {
                unset($this->headers[$pos]);
            }
            $this->headers['categoryFieldName'] = $extraMapping['categoryFieldName'];
        }
        if (!empty($extraMapping['familyFieldName'])) {
            if ($pos = array_search($extraMapping['familyFieldName'], $this->headers)) {
                unset($this->headers[$pos]);
            }
            $this->headers['familyFieldName'] = $extraMapping['familyFieldName'];
        }
        $this->shopifyClassObject = new ShopifyClass();
    }

    /**
      * {@inheritdoc}
      */
    public function write(array $items)
    {
        
        if (empty($this->headers)) {
            return null;
        }

        $parent = null;
        if ($this->storeTmpName === 'wk_akeneo_shopify_csv_export' && in_array('Handle', $this->headers)) {
            $parent = 'Handle';
        }
        
        $directory = $this->stepExecution->getJobExecution()->getExecutionContext()->get(\JobInterface::WORKING_DIRECTORY_PARAMETER);
        if (!$this->arrayConverter->stepExecution) {
            $this->arrayConverter->setStepExecution($this->stepExecution);
        }

        $flatItems = [];
        foreach ($items as $item) {
            if ($item['parent']) {
                if ($this->exportVariantAfterModel) {
                    $this->stepExecution->incrementSummaryInfo('write', 1);
                    continue;
                } elseif (empty($this->variantAxesField)) {
                    continue;
                }
            }
            
            if (isset($item['code'])) {
                $item['values']['sku'] [] = [
                    "locale" => null,
                    "scope" => null,
                    "data" => $item['code']
                ];
            }

            $flatProduct = $this->getFlatItems($item);
            $mainProductData = [];
            $mainProductmultipleImageField = $this->arrayConverter->multipleImageField;
            if (isset($flatProduct['data'])) {
                $mainProductData =  $flatProduct['data'];
                unset($flatProduct['data']);
            }
            if (!empty($flatProduct) && count($flatProduct) > 1) {
                $this->stepExecution->incrementSummaryInfo('write', - (count($flatProduct)-1));
            }
            $flatItems = array_merge($flatItems, $flatProduct);
            if (isset($item['code']) && $this->exportVariantAfterModel) {
                $matchedKey = null;
                foreach ($flatItems as $flatItemKey =>$flatItem) {
                    if (isset($flatProduct[0][0])
                    && isset($flatItem[0])
                    && $flatProduct[0][0] === $flatItem[0]) {
                        $matchedKey = (string) $flatItemKey;
                        break;
                    }
                }
              
                if ($matchedKey != null && $variantProducts = $this->connectorService->getProductModelVariants($item['code'])) {
                    if (!$this->productCsvProcessor->stepExecution) {
                        $this->productCsvProcessor->stepExecution = $this->stepExecution;
                    }
                    $firstVariant = true;
                    foreach ($variantProducts as $variantProduct) {
                        if (!$variantProduct) {
                            continue;
                        }
                        $this->variantProductCount++;
                        $standardVariantProduct = $this->productCsvProcessor->process($variantProduct);
                        $flatVariantProduct = $this->getFlatItems($standardVariantProduct);
                        $variantProductData = [];
                        if (isset($flatVariantProduct['data'])) {
                            $variantProductData =  $flatVariantProduct['data'];
                            unset($flatVariantProduct['data']);
                        }
                        if ($this->storeTmpName === 'wk_akeneo_shopify_csv_export') {
                            if ($firstVariant) {
                                foreach ($this->headers as $headerKey => $headerVal) {
                                    if ((
                                        (string) stripos($headerVal, 'Variant') != null
                                        || (string) stripos($headerVal, 'Option') != null
                                    )
                                        && isset($flatItems[$matchedKey][$headerKey])
                                        && isset($flatVariantProduct[0][$headerKey])) {
                                        $flatItems[$matchedKey][$headerKey] = $flatVariantProduct[0][$headerKey];
                                    }
                                }
                                $firstVariant = false;
                                $flatVariantProduct = [];
                            } else {
                                if (!empty($this->variantAxesField)) {
                                    foreach ($this->variantAxesField as $variantAxeKey => $variantAxeVal) {
                                        $position = (string) array_search($variantAxeVal, $this->headers);
                                        if ((string) stripos($variantAxeVal, 'name') != null
                                        && $position != null
                                        && isset($flatVariantProduct[0][$position])
                                        ) {
                                            $flatVariantProduct[0][$position] = '';
                                        }
                                    }
                                }
                                $this->stepExecution->incrementSummaryInfo('write', - 1);
                            }
                            $multipleImageField = $this->arrayConverter->multipleImageField;
                            /** Create multiple rows of image if image field are multiple for Shopify*/
                            if ($parent && $multipleImageField) {
                                $this->addMultipleImage($flatItems, $parent, $multipleImageField, $variantProductData);
                            }
                        }

                        $flatItems = array_merge($flatItems, $flatVariantProduct);
                    }
                }
            }

            /** Create multiple rows of image if image field are multiple for Shopify*/
            if ($parent && $mainProductmultipleImageField && $this->storeTmpName === 'wk_akeneo_shopify_csv_export') {
                $this->addMultipleImage($flatItems, $parent, $mainProductmultipleImageField, $mainProductData);
            }
        }

        if ($this->withHeader && !$this->headerMerge) {
            $this->headerMerge = true;
            array_unshift($flatItems, $this->headers);
            $this->stepExecution->incrementSummaryInfo('write', - 1);
        }

        $this->flatRowBuffer->write($flatItems);
    }

    /**
     * Get flat product data from standard product
     *
     * @param array $flatItems
     * @param array $item
     *
     * @return array
     */
    public function getFlatItems(array $item): array
    {
        $item['values'] = $this->localizer->convertToLocalizedFormats($item['values'], $this->converterOptions);

        return $this->arrayConverter->convert($item, []);
    }

    /**
     * Add multiple image in csv
     *
     * @param array $flatItems;
     * @param string|null $flatItems;
     * @param array $multipleImageField
     * @param array $productData
     *
     */
    public function addMultipleImage(&$flatItems, $parent, $multipleImageField, $productData)
    {
        $multiRowsData = $this->shopifyClassObject->createMultiRow($parent, $multipleImageField, $productData, $this->headers);
        if (!empty($multiRowsData)) {
            foreach ($multiRowsData as $singleRowData) {
                $this->stepExecution->incrementSummaryInfo('write', - 1);
                array_push($flatItems, $singleRowData);
            }
        }
    }

    /**
    * Flush items into a file
    */
    public function flush()
    {
        if ($this->stepExecution->getStepName() === 'product_model_csv_export'
            && $this->exportVariantAfterModel
        ) {
            $this->stepExecution->incrementSummaryInfo('write', - $this->variantProductCount);
        }
        $this->negateExtraWrite();
        $this->flusher->setStepExecution($this->stepExecution);
        $writtenFiles = $this->flusher->flush(
            $this->flatRowBuffer,
            $this->getWriterConfiguration(),
            $this->getPath(),
            ($this->parameters->has('linesPerFile') ? $this->parameters->get('linesPerFile') : -1)
        );

        foreach ($writtenFiles as $writtenFile) {
            $this->writtenFiles[$writtenFile] = basename($writtenFile);
        }
    }

    /**
    * Get the file path in which to write the data
    *
    * @param array $placeholders
    *
    * @return string
    */
    public function getPath(array $placeholders = [])
    {
        $filePath = $this->parameters->get('filePath');

        if (false !== strpos($filePath, '%')) {
            $datetime = $this->stepExecution->getStartTime()->format($this->datetimeFormat);
            $defaultPlaceholders = ['%datetime%' => $datetime, '%job_label%' => ''];
            $jobExecution = $this->stepExecution->getJobExecution();

            if (isset($placeholders['%job_label%'])) {
                $placeholders['%job_label%'] = $this->sanitize($placeholders['%job_label%']);
            } elseif (null !== $jobExecution->getJobInstance()) {
                $defaultPlaceholders['%job_label%'] = $this->sanitize($jobExecution->getJobInstance()->getLabel());
            }
            $replacePairs = array_merge($defaultPlaceholders, $placeholders);
            $filePath = strtr($filePath, $replacePairs);
        }

        return $filePath;
    }

    /**
     * {@inheritdoc}
     */
    public function getWrittenFiles()
    {
        return $this->writtenFiles;
    }

    /**
     * {@inheritdoc}
     */
    public function setStepExecution(\StepExecution $stepExecution)
    {
        $this->stepExecution = $stepExecution;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getWriterConfiguration()
    {
        return [
            'type'           => 'csv',
            'fieldDelimiter' => $this->parameters->get('delimiter'),
            'fieldEnclosure' => $this->parameters->get('enclosure'),
            'shouldAddBOM'   => false,
        ];
    }

    /**
     * {@inheritdoc}
     */
    protected function getItemIdentifier(array $product)
    {
        return isset($product['identifier']) ? $product['identifier'] : $product['code'];
    }

    /**
    * Replace [^A-Za-z0-9\.] from a string by '_'
    *
    * @param string $value
    *
    * @return string
    */
    protected function sanitize($value)
    {
        return preg_replace('#[^A-Za-z0-9\.]#', '_', $value);
    }

    /**
     * @return array
     */
    protected function setConverterOptions(\JobParameters $parameters)
    {
        if ($parameters->has('decimalSeparator')) {
            $this->converterOptions['decimal_separator'] = $parameters->get('decimalSeparator');
        }

        if ($parameters->has('dateFormat')) {
            $this->converterOptions['date_format'] = $parameters->get('dateFormat');
        }

        if ($parameters->has('ui_locale')) {
            $this->converterOptions['locale'] = $parameters->get('ui_locale');
        }
    }

    /**
     * Remove extra write
     */
    protected function negateExtraWrite()
    {
        /* correct write summary log in same file for product as well as models */
        if ($this->flatRowBuffer->key()
            && !$this->productExportStarted
        ) {
            $this->stepExecution->incrementSummaryInfo('write', -1*$this->flatRowBuffer->key());
            $this->productExportStarted = true;
        }
    }
}
