<?php
declare(strict_types=1);

namespace Leadlion\Autogedal\Model\Import;

use Magento\CatalogImportExport\Model\StockItemImporterInterface;
use Magento\CatalogInventory\Model\ResourceModel\Stock\Item;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;

#[\AllowDynamicProperties]
class Product extends \Firebear\ImportExport\Model\Import\Product
{
    /**
     * @param array $data
     *
     * @return array
     */
    public function joinIdenticalyData($data)
    {
        $reverts = [];
        foreach ($this->_parameters['map'] as $item) {
            if ($item['import']) {
                $reverts[$item['import']][] = $item['system'];
            }
        }
        if (!empty($this->_parameters['identicaly'])) {
            foreach ($this->_parameters['identicaly'] as $elem) {
                if (!empty($data[$reverts[$elem['import']][0]])) {
                    if (!in_array($elem['system'], ["volum_l_cutie_pe_bare"])) {
                        $data[$elem['system']] = $data[$reverts[$elem['import']][0]];
                    } 
                }
            }
        }
        return $data;
    }

     /**
     * Replace attribute values according map
     *
     * @param array $rowData
     *
     * @return array
     * @throws NoSuchEntityException
     */
    protected function attributeValuesMapping($rowData)
    {
        if (empty($this->jobsCache[$this->_parameters['job_id']])) {
            $this->jobsCache[$this->_parameters['job_id']] =
                $this->jobRepository->getById($this->_parameters['job_id']);
        }
        $job = $this->jobsCache[$this->_parameters['job_id']];
        $map = $this->phpUnserialize($job->getMapping());

        foreach ($map as $item) {
            if (isset($item['source_data_attribute_value_system']) &&
                isset($item['source_data_attribute_value_import'])
            ) {
                foreach ($rowData as $key => $value) {
                    if (!is_array($value) && 
                        ($value && trim((string)$value) == trim($item['source_data_attribute_value_import'])) &&
                        !in_array($key, ["volum_l_cutie_pe_bare", "Volum (L)"])
                    ) {
                        $rowData[$key] = $item['source_data_attribute_value_system'];
                    }
                }
            }
        }

        return $rowData;
    }

    /**
     * @var StockItemImporterInterface
     */
    private $stockItemImporter;

    /**
     * @var array|null Cached SKUs of products with manufacturer = Fabbri
     */
    private ?array $fabbriSkus = null;

    /**
     * Returns SKUs of all products whose manufacturer attribute label is "Fabbri".
     * Result is loaded once via SQL and cached for this import instance.
     *
     * @return array
     */
    private function getFabbriSkus(): array
    {
        if ($this->fabbriSkus !== null) {
            return $this->fabbriSkus;
        }

        try {
            $select = $this->_connection->select()
                ->distinct(true)
                ->from(['cpe' => $this->_connection->getTableName('catalog_product_entity')], ['sku'])
                ->joinInner(
                    ['cpev' => $this->_connection->getTableName('catalog_product_entity_varchar')],
                    'cpe.entity_id = cpev.entity_id',
                    []
                )
                ->joinInner(
                    ['ea' => $this->_connection->getTableName('eav_attribute')],
                    "cpev.attribute_id = ea.attribute_id AND ea.attribute_code = 'manufacturer'",
                    []
                )
                ->joinInner(
                    ['eaov' => $this->_connection->getTableName('eav_attribute_option_value')],
                    "cpev.value = eaov.option_id AND eaov.store_id = 0 AND eaov.value = 'Fabbri'",
                    []
                );

            $this->fabbriSkus = $this->_connection->fetchCol($select);

            $count = count($this->fabbriSkus);
            $this->addLogWriteln(
                "[Fabbri] Loaded {$count} Fabbri SKUs from DB: " . implode(', ', $this->fabbriSkus),
                $this->getOutput(),
                'info'
            );
        } catch (\Exception $e) {
            $this->fabbriSkus = [];
            $this->addLogWriteln(
                '[Fabbri] ERROR loading Fabbri SKUs: ' . $e->getMessage(),
                $this->getOutput(),
                'error'
            );
        }

        return $this->fabbriSkus;
    }

    /**
     * @param $rowData
     * @return mixed
     * @throws NoSuchEntityException
     */
    protected function applyPriceRules($rowData)
    {
        $priceExcludedSkus = [
            '6503134',
            '6503135',
            '6503139',
            '6503138'
        ];

        $sku = $rowData[self::COL_SKU] ?? '';
        if (in_array($sku, $priceExcludedSkus)) {
            unset($rowData['price'], $rowData['special_price']);
            return $rowData;
        }

        // Exclude price updates for products with manufacturer = Fabbri
        if (in_array($sku, $this->getFabbriSkus())) {
            $originalPrice = $rowData['price'] ?? 'n/a';
            $originalSpecial = $rowData['special_price'] ?? 'n/a';
            $this->addLogWriteln(
                "[Fabbri] SKU {$sku}: price excluded (was price={$originalPrice}, special_price={$originalSpecial})",
                $this->getOutput(),
                'info'
            );
            unset($rowData['price'], $rowData['special_price']);
            return $rowData;
        }

        if (!empty($this->_parameters['price_rules'])) {
            $priceRules = $this->_parameters['price_rules'];

            foreach ($priceRules as $priceRule) {
                $applyRule = true;

                if (isset($priceRule['price_rules_conditions_hidden']['rule']['conditions'])) {
                    $setId = null;
                    if (isset($rowData['_attribute_set']) &&
                        isset($this->_attrSetNameToId[$rowData['_attribute_set']])
                    ) {
                        $setId = $this->_attrSetNameToId[$rowData['_attribute_set']];
                    }

                    $rowScope = empty($rowData[self::COL_STORE]) ? self::SCOPE_DEFAULT : self::SCOPE_STORE;
                    $storeId = (self::SCOPE_STORE == $rowScope)
                        ? $this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE])
                        : 0;

                    $data = [
                        'conditions' => $priceRule['price_rules_conditions_hidden']['rule'],
                        'row' => $rowData,
                        'attribute_set_id' => $rowData['attribute_set_id'] ?? $setId,
                        'store_id' => $rowData['store_id'] ?? $storeId,
                        'categories' => isset($this->categoriesCache[$this->getCorrectSkuAsPerLength($rowData)]) ?
                            array_keys($this->categoriesCache[$this->getCorrectSkuAsPerLength($rowData)]) : [],
                    ];

                    if (empty($data['categories'])) {
                        $checkSku = $this->getCorrectSkuAsPerLength($rowData);
                        if (isset($this->_oldSku[strtolower($checkSku)])) {
                            /** @var \Magento\Catalog\Model\Product $product */
                            $product = $this->productRepository->get($checkSku);
                            $data['categories'] = $product->getCategoryIds();
                        }
                    }
                    $applyRule = $this->priceRuleConditionFactory->create()->validatePriceRuleConditions($data);
                }

                if ($applyRule && isset($priceRule['apply'])) {
                    if ($priceRule['apply'] == 'fixed') {
                        $rowData['price'] += $priceRule['value'];
                    } else {
                        $rowData['price'] *= 1 + $priceRule['value'] / 100;
                    }
                }
            }
        }

        if (isset($this->_parameters['round_up_prices'], $rowData['price'])
            && $this->_parameters['round_up_prices'] > 0) {
            $rowData['price'] = $this->roundPrice($rowData['price']);
        }

        if (isset($this->_parameters['round_up_special_price'], $rowData['special_price'])
            && $this->_parameters['round_up_special_price'] > 0) {
            $rowData['special_price'] = $this->roundPrice($rowData['special_price']);
        }

        if (isset($rowData['Pret_achizitie_cu_TVA_inclus']) &&
            isset($rowData['Pret_cu_TVA']) &&
            $rowData['Pret_achizitie_cu_TVA_inclus'] > $rowData['Pret_cu_TVA']
        ) {
            $rowData['price'] *= 1 + 10 / 100;
        }

        if (isset($rowData['Pret_cu_TVA']) &&
            $rowData['Pret_cu_TVA'] == 0 &&
            isset($rowData['Pret_achizitie_cu_TVA_inclus'])
        ) {
            $rowData['price'] = $rowData['Pret_achizitie_cu_TVA_inclus'];
            $rowData['price'] *= 1 + 10 / 100;
        }

        return $rowData;
    }

    /**
     * @param $rowData
     * @param $mediaGallery
     * @param $existingImages
     * @param $uploadedImages
     * @param $rowNum
     * @param $existingAttributeImages
     *
     * @throws LocalizedException
     */
    protected function processMediaGalleryRows(
        &$rowData,
        &$mediaGallery,
        &$existingImages,
        &$uploadedImages,
        $rowNum,
        $existingAttributeImages
    )
    {
        $this->importImageProcessor->processMediaGalleryRows(
            $rowData,
            $mediaGallery,
            $existingImages,
            $uploadedImages,
            $rowNum,
            $existingAttributeImages
        );
    }

    public function getAttributeOptions(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute, $indexValAttrs = [])
    {
        $stores = $this->getStoreWithCodes();
        $stores[0] = 'admin'; // We add admin store here for backward compatibility
        $attributeOptions = \Magento\ImportExport\Model\Import\Entity\AbstractEntity::getAttributeOptions($attribute, $indexValAttrs);
        $options = $attributeOptions;
        foreach ($stores as $id => $code) {
            $attribute->setStoreId($id);
            $options[$code] = $attributeOptions;
            try {
                if ($this->swatchesHelperData->isTextSwatch($attribute)) {
                    $swatchOptionsIds = array_values($options[$code]);
                    $swatchOption = $this->getSwatchesByOptionsId($swatchOptionsIds, $attribute->getAttributeId());
                    foreach ($swatchOption as $optionId => $optionValue) {
                        if (isset($optionValue['value'])
                            && ($optionValue['value'] !== '' || $optionValue['value'] !== null)
                        ) {
                            $options[$code][strtolower($optionValue['value'])] = $optionId;
                        }
                    }
                }
            } catch (Exception $e) {
                $this->addLogWriteln($e->getMessage(), $this->getOutput(), 'error');
            }
        }
        return $options;
    }

    /**
     * Save Stock Item.
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function _saveStockItem()
    {
        if (!$this->stockItemImporter && interface_exists(StockItemImporterInterface::class)) {
            $this->stockItemImporter = ObjectManager::getInstance()
                ->get(\Firebear\ImportExport\Model\Import\StockItemImporterInterface::class);
        }

        /** @var $stockResource Item */
        $stockResource = $this->_stockResItemFac->create();
        $entityTable = $stockResource->getMainTable();
        $toSetMsiSourceData = true;
        $oosProducts = [];
        $importedSkus = [];

        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
            if ($this->sourceManager->isEnableMsi() && $this->sourceManager->getCoreFields($bunch)) {
                $toSetMsiSourceData = false;
            }
            $stockData = [];
            $msiSourceData = [];

            $dataForSkuFz = [];

            // Format bunch to stock data rows
            foreach ($bunch as $rowNum => $rowData) {
                if (!$this->isRowAllowedToImport($rowData, $rowNum)) {
                    continue;
                }
                if (isset($this->_parameters['increase_product_stock_by_qty'])
                    && $this->_parameters['increase_product_stock_by_qty'] == 1
                ) {
                    $rowData['qty'] = (int)($rowData['qty'] + $this->getProductStockQty($rowData[self::COL_SKU]));
                }

                $rowData['is_in_stock'] = 1;
                
                $checkSku = $this->getCorrectSkuAsPerLength($rowData);
                if (isset($this->_oldSku[strtolower($checkSku)])) {
                    /** @var \Magento\Catalog\Model\Product $product */
                    $product = $this->productRepository->get($checkSku);
                    $manuallyOutOfStock = $product->getManuallyOutOfStock();
                    if (isset($rowData['qty']) && $manuallyOutOfStock && $rowData['qty'] == 0) {
                        $rowData['is_in_stock'] = 0;
                    }
                }

                

                $sku = $rowData[self::COL_SKU];

                // Exclude stock updates for products with manufacturer = Fabbri
                if (in_array($sku, $this->getFabbriSkus())) {
                    $originalQty = $rowData['qty'] ?? 'n/a';
                    $this->addLogWriteln(
                        "[Fabbri] SKU {$sku}: stock excluded (feed qty={$originalQty})",
                        $this->getOutput(),
                        'info'
                    );
                    continue;
                }

                $row = [];
                if ($this->skuProcessor->getNewSku($sku) !== null
                    && $this->checkIfStockAttributesExist($rowData)
                ) {
                    $row = $this->formatStockDataForRow($rowData);
                }
                if (isset($rowData['msi_source_code']) && !empty($rowData['msi_source_code']) && $toSetMsiSourceData) {
                    $msiSourceData[$sku] = $rowData['msi_source_code'];
                }
                if (
                    !isset($stockData[$sku]) &&
                    !empty($row) &&
                    !in_array($sku, 
                        [
                            '6950251',
                            '6950259',
                            '6201837',
                            '6201837N',
                            '6201837VW',
                            '6201837RN',
                            '6201842',
                            '6201844',
                            '6201838',
                            '6201960',
                            '6801900',
                            '6801898',
                            '6801896',
                            '6801882',
                            '6801884',
                            '6801886',
                            '6801888',
                            '6801864',
                            '6801863',
                            '6503135',
                            '6503134',
                            '6503138',
                            '6503139',
                            '6503140',
                            '6503145',
                            '6503146',
                            '6503137',
                            '6503136',
                            'HK0846',
                            'HV0815',
                            'HV0850',
                            'HV0870',
                            'HV2400',
                            'HV2450',
                            'HV4500',
                            'HV4502',
                            'HV4600',
                            'HV4602'
                        ]
                    )
                ) {
                    $stockData[$sku] = $row;
                }
            }

            foreach ($stockData as $sku => $data) {
                $dataForSkuFz[] = [
                    'job_id' => $this->_parameters['job_id'],
                    'sku' => $sku,
                    'qty' => $data['qty']
                ];
                $oosProducts[] = $sku;
            }
            
            // Collect only SKUs where qty or is_in_stock actually changed
            $entityIdToSku = [];
            foreach (array_keys($stockData) as $sku) {
                $lowerSku = strtolower((string)$sku);
                if (isset($this->_oldSku[$lowerSku]['entity_id'])) {
                    $entityIdToSku[(int)$this->_oldSku[$lowerSku]['entity_id']] = $sku;
                }
            }
            if (!empty($entityIdToSku)) {
                $currentStockRows = $this->_connection->fetchAll(
                    $this->_connection->select()
                        ->from($entityTable, ['product_id', 'qty', 'is_in_stock'])
                        ->where('product_id IN (?)', array_keys($entityIdToSku))
                );
                $currentStockMap = [];
                foreach ($currentStockRows as $row) {
                    $currentStockMap[(int)$row['product_id']] = $row;
                }
                foreach ($entityIdToSku as $entityId => $sku) {
                    $newData   = $stockData[$sku];
                    $current   = $currentStockMap[$entityId] ?? null;
                    $qtyChanged = $current === null
                        || (float)$current['qty'] !== (float)($newData['qty'] ?? 0)
                        || (int)$current['is_in_stock'] !== (int)($newData['is_in_stock'] ?? 0);
                    if ($qtyChanged) {
                        $importedSkus[] = $sku;
                    }
                }
            }

            // Insert rows
            if (!empty($stockData)) {
                if ($this->stockItemImporter instanceof StockItemImporterInterface
                    && version_compare($this->productMetadata->getVersion(), '2.3.0', '>=')
                ) {
                    try {
                        if ($toSetMsiSourceData) {
                            $this->stockItemImporter->setSourceData($msiSourceData);
                        }
                        $this->stockItemImporter->import($stockData);

                        //@todo add a config for job id
                        if ($this->_parameters['job_id'] == 28) {
                            $this->_connection->insertOnDuplicate(
                                'leadlion_imported_skus',
                                $dataForSkuFz);
                        }

                    } catch (Exception $exception) {
                        $this->addLogWriteln($exception->getMessage(), $this->getOutput(), 'info');
                        $this->getLogger()->debug($exception);
                    }
                } else {
                    $this->_connection->insertOnDuplicate($entityTable, array_values($stockData));
                }
            }
        }
        if (isset($this->_parameters['job_id']) &&
            isset($this->_parameters['oos_missing_products']) &&
            $this->_parameters['oos_missing_products'] == 1
        ) {
            $existingProdInfo = $this->getProdInfo($this->_parameters['job_id']);
            if (!empty($existingProdInfo) &&
                isset($existingProdInfo[0])
            ) {
                try {
                    $existingProducts = \Laminas\Serializer\Serializer::unserialize($existingProdInfo[0]['oos_products']);
                } catch (\Exception) {
                    $existingProducts = [];
                }
                $productsssInfo = $this->array_merge_recursive_distinct($existingProducts, $oosProducts);

                $info = [
                    'job_id' => $this->_parameters['job_id'],
                    'oos_products' => \Laminas\Serializer\Serializer::serialize($productsssInfo)
                ];
            } else {
                $info = [
                    'job_id' => $this->_parameters['job_id'],
                    'oos_products' => \Laminas\Serializer\Serializer::serialize($oosProducts)
                ];
            }
            $this->_connection->insertOnDuplicate(
                'leadlion_oos_products',
                $info
            );

        }

        if (!empty($importedSkus)) {
            $this->invalidateVarnishForImportedProducts($importedSkus);
        }

        return $this;
    }

    /**
     * Send targeted Varnish PURGE for products updated during stock import.
     * Uses entity IDs already loaded in $_oldSku — no extra DB query.
     *
     * @param string[] $skus
     * @return void
     */
    private function invalidateVarnishForImportedProducts(array $skus): void
    {
        try {
            /** @var \Magento\PageCache\Model\Config $pageCacheConfig */
            $pageCacheConfig = ObjectManager::getInstance()->get(\Magento\PageCache\Model\Config::class);
            if (!$pageCacheConfig->isEnabled() ||
                (int)$pageCacheConfig->getType() !== \Magento\PageCache\Model\Config::VARNISH
            ) {
                return;
            }

            $tags = [];
            $pattern = '((^|,)%s(,|$))';
            foreach ($skus as $sku) {
                $lowerSku = strtolower((string)$sku);
                if (isset($this->_oldSku[$lowerSku]['entity_id'])) {
                    $tags[] = sprintf($pattern, 'cat_p_' . (int)$this->_oldSku[$lowerSku]['entity_id']);
                }
            }

            if (empty($tags)) {
                return;
            }

            /** @var \Magento\CacheInvalidate\Model\PurgeCache $purgeCache */
            $purgeCache = ObjectManager::getInstance()->get(\Magento\CacheInvalidate\Model\PurgeCache::class);
            $purgeCache->sendPurgeRequest(array_unique($tags));

            ObjectManager::getInstance()
                ->get(\Leadlion\Autogedal\Logger\VarnishPurge::class)
                ->info('[Firebear] PURGE sent for ' . count($skus) . ' SKUs: ' . implode(', ', $skus));
        } catch (\Exception $e) {
            // non-critical — nu oprim importul daca PURGE esueaza
        }
    }

    private function array_merge_recursive_distinct()
    {
        $arrays = func_get_args();
        $base = array_shift($arrays);
        if (!is_array($base)) {
            $base = empty($base) ? [] : [$base];
        }
        foreach ($arrays as $append) {
            if (!is_array($append)) {
                $append = [$append];
            }
            foreach ($append as $key => $value) {
                if (!array_key_exists($key, $base) and !is_numeric($key)) {
                    $base[$key] = $append[$key];
                    continue;
                }
                if (is_array($value) || is_array($base[$key])) {
                    $base[$key] = $this->array_merge_recursive_distinct($base[$key], $append[$key]);
                } else {
                    if (is_numeric($key)) {
                        if (!in_array($value, $base)) {
                            $base[] = $value;
                        }
                    } else {
                        $base[$key] = $value;
                    }
                }
            }
        }
        return $base;
    }
    
    private function getProdInfo($jobId)
    {
        $oosProdTable = $this->_resource->getTable('leadlion_oos_products');

        $select = $this->_connection->select();
        $select->from(
            ['e' => $oosProdTable],
            ['id', 'job_id', 'oos_products']
        )->where(
            'e.job_id = ?',
            $jobId
        );
        $info = $this->_connection->fetchAll($select);
        return $info;
    }
}
