<?php

declare(strict_types=1);

namespace Webkul\WebkulCsvImportBundle\Connector\Writer;

use Webkul\WebkulCsvImportBundle\Services\WebkulCsvConnector;

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

/**
 * Write product data into a XLSX file on the local filesystem
 */
class ProductXlsxWriter extends \AbstractItemMediaWriter implements
    \ItemWriterInterface,
    \InitializableInterface,
    \FlushableInterface,
    \StepExecutionAwareInterface,
    \ArchivableWriterInterface
{

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

    /** @var ChannelRepositoryInterface */
    protected $channelRepository;

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

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

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

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

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

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

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

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

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

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

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

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

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

    protected $familyCount;

    protected $extraMapping;
    
    /**
     * @param \ChannelRepositoryInterface         $channelRepository,
     * @param \ArrayConverterInterface            $arrayConverter
     * @param \BufferFactory                      $bufferFactory
     * @param \FlatItemBufferFlusher              $flusher
     * @param \AttributeRepositoryInterface       $attributeRepository
     * @param \FileExporterPathGeneratorInterface $fileExporterPathss
     * @param array                               $mediaAttributeTypes
     * @param WebkulCsvConnector                  $connectorService
     */
    public function __construct(
        \ChannelRepositoryInterface $channelRepository,
        \ArrayConverterInterface $arrayConverter,
        \BufferFactory $bufferFactory,
        \FlatItemBufferFlusher $flusher,
        \AttributeRepositoryInterface $attributeRepository,
        \FileExporterPathGeneratorInterface $fileExporterPath,
        array $mediaAttributeTypes,
        WebkulCsvConnector $connectorService
    ) {
        parent::__construct(
            $arrayConverter,
            $bufferFactory,
            $flusher,
            $attributeRepository,
            $fileExporterPath,
            $mediaAttributeTypes
        );
        $this->channelRepository = $channelRepository;
        $this->connectorService = $connectorService;
    }

    /**
     * {@inheritdoc}
     */
    public function initialize()
    {
        parent::initialize();
        $this->connectorService->setStepExecution($this->stepExecution);
        $parameters = $this->stepExecution->getJobParameters()->all();
        if (empty($parameters['exportProfile'])) {
            throw new \Exception(sprintf("Please select template store first"));
        }
      
        $templateStore = $this->connectorService->getTemplateStore($parameters['exportProfile']);
        $storeTemplateData       = json_decode($templateStore->getRawValues(), true);
        $storeTemplateData       = isset($storeTemplateData['xlsxExportMapping']) ? $storeTemplateData['xlsxExportMapping'] : [];
        $this->storeTemplateData = isset($storeTemplateData['rawValues']) ? $storeTemplateData['rawValues']: [];
        if (empty($this->storeTemplateData)) {
            throw new \Exception(sprintf("Template xlsx mapping is empty"));
        }

        $this->locales = $parameters['filters']['structure']['locales'];
        $this->scope = $parameters['filters']['structure']['scope'];
        $channelData = $this->channelRepository->findOneByIdentifier($this->scope);
        $currencies = $channelData->getCurrencies() ? $channelData->getCurrencies()->getValues() : [];
        $this->currencies = [];
        foreach ($currencies as $curreny) {
            $this->currencies [] = $curreny->getCode();
        }
        $defaultMapping = array_column($this->storeTemplateData, 'defaultValue', 'name');
        $channelData = $this->channelRepository->findOneByIdentifier($this->scope);
        $this->extraMapping  = isset($storeTemplateData['extraMapping']) ? $storeTemplateData['extraMapping'] : [];
        $this->categoriesSeperator = isset($parameters['WebkulXlsxCategorySeperator']) ? $parameters['WebkulXlsxCategorySeperator'] : ',';
        $this->multiValueSeperator = !empty($this->extraMapping['multiValueSeperator']) ? $this->extraMapping['multiValueSeperator'] : ',';
        $this->exportMetricUnit    = !empty($this->extraMapping['exportMetricUnit']) ? $this->extraMapping['exportMetricUnit'] : 'false';
        $this->seperateFamily    = !empty($this->extraMapping['seperateFamily']) ? $this->extraMapping['seperateFamily'] : 'false';
        $this->setAttributeMapping($defaultMapping);
        $headers = array_keys($this->simpleProductMapping);
       
        if (!empty($this->extraMapping['enabledFieldName'])) {
            $this->extraHeaders['enabledFieldName'] = $this->extraMapping['enabledFieldName'];
        }
        if (!empty($this->extraMapping['categoryFieldName'])) {
            $this->extraHeaders['categoryFieldName'] = $this->extraMapping['categoryFieldName'];
        }
        if (!empty($this->extraMapping['parentFieldName'])) {
            $this->extraHeaders['parentFieldName'] = $this->extraMapping['parentFieldName'];
        }
        if (!empty($this->extraMapping['familyFieldName'])) {
            $this->extraHeaders['familyFieldName'] = $this->extraMapping['familyFieldName'];
        }
      
        if (!empty($this->extraMapping['familyVariantColumn'])) {
            $this->extraHeaders['familyVariantColumn'] = $this->extraMapping['familyVariantColumn'];
        }
        
        $this->headers = array_merge($headers, array_unique(array_values($this->extraHeaders)));
    }

    /**
     * {@inheritdoc}
     */
    public function write(array $items)
    {
        $parameters = $this->stepExecution->getJobParameters();
        $converterOptions = $this->getConverterOptions($parameters);
        $flatItems = [];
        foreach ($items as $item) {
            unset($item['associations']);
            $flatItem = $this->arrayConverter->convert($item, $converterOptions);
            if (isset($flatItem['code'])) {
                $flatItem['sku'] = $flatItem['code'];
            }

            $flatItems[] = $this->formatItem($flatItem);
        }
        $options = [];
        $options['withHeader'] = $parameters->get('withHeader');
        $this->flatRowBuffer->write($flatItems, $options);
    }

    /**
     * {@inheritdoc}
     */
    public function flush()
    {
        $parameters = $this->stepExecution->getJobParameters();
        if ($this->familyCount > 1
            && !empty($this->extraMapping['familyFieldName'])
        ) {
            $count = 1;
            while ($count <= $this->familyCount) {
                $this->headers[] = $this->extraMapping['familyFieldName'].$count;
                $count++;
            }
        }
        if ($parameters->has('withHeader')
            && true === $parameters->get('withHeader')
        ) {
            $this->flatRowBuffer->addToHeaders($this->headers);
        }

        parent::flush();
    }

    protected function formatItem($item)
    {
        $formattedItem = [];
        $extraData = [];
        $mapping = $this->simpleProductMapping;
 
        if (isset($item['parent'])
            && $item['parent']
        ) {
            $mapping = $this->variantProductMappping;
        }
        if (isset($this->extraMapping['enabledFieldName'])) {
            $extraData[$this->extraMapping['enabledFieldName']] = isset($item['enabled']) && $item['enabled'] == 1 ? 'true' : 'false';
            unset($item['enabled']);
        }
        if (isset($this->extraMapping['categoryFieldName'])) {
            $extraData[$this->extraMapping['categoryFieldName']] = str_replace(',', $this->categoriesSeperator, $item['categories']);
            unset($item['categories']);
        }
        
        if (isset($this->extraMapping['parentFieldName'])) {
            $extraData[$this->extraMapping['parentFieldName']] = isset($item['parent']) ? $item['parent'] : '';
            if (isset($item['parent'])) {
                unset($item['parent']);
            }
        }

        if (isset($this->extraMapping['familyFieldName'])) {
            if (isset($item['family_variant'])) {
                $family = $this->connectorService->getFamilyByFamilyVariant($item['family_variant']);
                $familyCode = $family ? $family->getFamily()->getCode() : '';
                $extraData[$this->extraMapping['familyVariantColumn']] = $item['family_variant'];                
                unset($item['family_variant']);
            } else {
                $familyCode = $item['family'];
                unset($item['family']);
            }

            if ($this->seperateFamily == 'true') {
                $familyCodes = explode('_', $familyCode);
                foreach ($familyCodes as $fCodeKey => $fCode) {
                    if ($fCodeKey == 0) {
                        $extraData[$this->extraMapping['familyFieldName']] = $fCode;
                    } else {
                        $extraData[$this->extraMapping['familyFieldName'].$fCodeKey] = $fCode;
                    }
                }
                $this->familyCount = count($familyCodes);
            } else {
                $extraData[$this->extraMapping['familyFieldName']] = $familyCode;
            }
        }

        foreach ($mapping as $xlsxField => $akeneoField) {
            $attributeCode = $akeneoField;
            $xlsxFieldCode = $xlsxField;
            if (strpos($akeneoField, '-') !== false) {
                $attributeCode  = substr($akeneoField, 0, strpos($akeneoField, '-'));
            }
            if (strpos($xlsxFieldCode, '-') !== false) {
                $xlsxFieldCode  = substr($xlsxFieldCode, 0, strpos($xlsxFieldCode, '-'));
            }

            if (isset($item[$akeneoField])) {
                $attribute = $this->attributeRepository->findOneByIdentifier($attributeCode);
                $attributeType = $attribute->getType();
                $fieldValue = $item[$akeneoField];
                $formattedItem[$xlsxField] = $this->formatAttributeValue(
                    $xlsxField,
                    $akeneoField,
                    $attributeType,
                    $fieldValue,
                    $item
                );
            } elseif (isset($this->defaultMapping[$xlsxField])) {
                $fieldValue = $this->defaultMapping[$xlsxField];
                $formattedItem[$xlsxField] = $this->formatAttributeValue(
                    $xlsxField,
                    $xlsxField,
                    $this->storeTemplateData[$xlsxFieldCode]['attributeType'],
                    $fieldValue
                );
            } else {
                $formattedItem[$xlsxField] = '';
            }
        }
        
        return array_merge($formattedItem, $extraData);
    }

    /**
     * Format Attribute Value
     *
     * @param string $xlsxField
     * @param string $akeneoField
     * @param string $attributeType
     * @param string $fieldValue
     * @param array  $item
     */
    protected function formatAttributeValue(
        $xlsxField,
        $akeneoField,
        $attributeType,
        $fieldValue,
        $item = []
    ) {
        switch ($attributeType) {
            case 'pim_catalog_image':
                $fieldValue = $this->connectorService->generateImageUrl($fieldValue);
            break;

            case 'pim_catalog_multiselect':
                $fieldValue = str_replace(',', $this->multiValueSeperator, $fieldValue);
            break;

            case 'pim_catalog_metric':
                if ($this->exportMetricUnit != 'false') {
                    $fieldValue = isset($item[$akeneoField.'-unit']) ? $fieldValue. ' '. $item[$akeneoField.'-unit'] : $fieldValue;
                }
            break;

            case 'pim_catalog_boolean':
                if (isset($item['parent'])
                    && $item['parent']
                ) {
                    $format = $this->storeTemplateData[$xlsxField]['variantBooleanValueFormat'];
                } else {
                    $format = $this->storeTemplateData[$xlsxField]['productBooleanValueFormat'];
                }
                $fieldValue = $this->formatBooleanValue($format, $fieldValue);
            break;

            default:
        }

        return $fieldValue;
    }

    /**
     * Set Attribute mapping
     *
     * @param array $defaultMapping
     */
    protected function setAttributeMapping($defaultMapping)
    {
        $this->simpleProductMapping = [];
        $this->variantProductMappping = [];
        $this->defaultMapping = [];
        foreach ($this->storeTemplateData as $xlsxField => $fieldData) {
            $mapping = $this->formatAttribute(
                $fieldData['localizable'],
                $fieldData['scopable'],
                $fieldData['attributeType'],
                $xlsxField,
                $fieldData['attribute']
            );

            foreach ($mapping as $attributeMappingKey => $attributeMappingValue) {
                $this->defaultMapping[$attributeMappingKey] = $defaultMapping[$xlsxField] ? $defaultMapping[$xlsxField] : '';
            }

            $this->simpleProductMapping = array_merge($this->simpleProductMapping, $mapping);
            
            $variantMapping = $this->formatAttribute(
                $fieldData['localizable'],
                $fieldData['scopable'],
                $fieldData['attributeType'],
                $xlsxField,
                $fieldData['variantattribute']
            );

            $this->variantProductMappping = array_merge($this->variantProductMappping, $variantMapping);
        }
    }

    /**
     * Format xlsx field according to mapping
     *
     * @param bool   $localizable
     * @param bool   $scopable
     * @param string $attributeType
     * @param string $xlsxField
     * @param string $akeneoField
     * @param array  $mapping
     */
    protected function formatAttribute(
        $localizable,
        $scopable,
        $attributeType,
        $xlsxField,
        $akeneoField
    ) {
        $mapping = [];
        if ($localizable
            && $scopable
        ) {
            foreach ($this->locales as $locale) {
                if ($attributeType == 'pim_catalog_price_collection') {
                    foreach ($this->currencies as $currency) {
                        $mapping[$xlsxField.'-'.$locale.'-'.$this->scope.'-'.$currency] = $akeneoField ? $akeneoField.'-'.$locale.'-'.$this->scope.'-'.$currency : '';
                    }
                } else {
                    $mapping[$xlsxField.'-'.$locale.'-'.$this->scope] = $akeneoField ? $akeneoField.'-'.$locale.'-'.$this->scope : '';
                }
            }
        } elseif ($localizable) {
            foreach ($this->locales as $locale) {
                if ($attributeType == 'pim_catalog_price_collection') {
                    foreach ($this->currencies as $currency) {
                        $mapping[$xlsxField.'-'.$locale.'-'.$currency] = $akeneoField ? $akeneoField.'-'.$locale.'-'.$currency : '';
                    }
                } else {
                    $mapping[$xlsxField.'-'.$locale] = $akeneoField ? $akeneoField.'-'.$locale : '';
                }
            }
        } elseif ($scopable) {
            if ($attributeType == 'pim_catalog_price_collection') {
                foreach ($this->currencies as $currency) {
                    $mapping[$xlsxField.'-'.$this->scope.'-'.$currency] = $akeneoField ? $akeneoField.'-'.$this->scope.'-'.$currency : '';
                }
            } else {
                $mapping[$xlsxField.'-'.$this->scope] = $akeneoField ? $akeneoField.'-'.$this->scope : '';
            }
        } else {
            if ($attributeType == 'pim_catalog_price_collection') {
                foreach ($this->currencies as $currency) {
                    $mapping[$xlsxField.'-'.$currency] = $akeneoField ? $akeneoField.'-'.$currency : '';
                }
            } else {
                $mapping[$xlsxField] = $akeneoField ? $akeneoField : '';
            }
        }

        return $mapping;
    }

    /**
     * {@inheritdoc}
     */
    protected function getWriterConfiguration()
    {
        return ['type' => 'xlsx'];
    }

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

    /**
    * Format boolean value by given format
    *
    * @param string format
    * @param bool|string $value
    */
    protected function formatBooleanValue(string $format, $value)
    {
        switch ($format) {
            case 'Yes/No':
                $value = $value ? 'Yes' : 'No';
                break;
            case 'Y/N':
                $value = $value ? 'Y' : 'N';
                break;
            case 'yes/no':
                $value = $value ? 'yes' : 'no';
                break;

            case 'True/False':
                $value = $value ? 'True' : 'False';
                break;

            case '1/0':
                $value = $value ? '1' : '0';
                break;

            case '1/2':
                $value = $value ? '1' : '2';
                break;
            default:
        }

        return $value;
    }
}
