<?php
declare(strict_types=1);

namespace Autogedal\Manufacturers\Service;

use Autogedal\Manufacturers\Api\Data\ManufacturerPageInterface;
use Autogedal\Manufacturers\Api\Data\ManufacturerPageInterfaceFactory;
use Autogedal\Manufacturers\Api\ManufacturerPageRepositoryInterface;
use Autogedal\Manufacturers\Model\Config as ManufacturersConfig;
use Autogedal\Manufacturers\Model\ResourceModel\ManufacturerPage\CollectionFactory as ManufacturerPageCollectionFactory;
use Magento\Cms\Model\Page;
use Magento\Cms\Model\PageFactory;
use Magento\Cms\Model\ResourceModel\Page as PageResource;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory as OptionCollectionFactory;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\File\Csv;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\ReadInterface;
use Magento\Framework\Lock\LockManagerInterface;
use Magento\Framework\Module\Dir\Reader as ModuleReader;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;

class ManufacturerGenerator
{
    public const LOCK_NAME = 'autogedal_manufacturers_generation';
    public const LOCK_TIMEOUT_SECONDS = 5;

    private const CMS_PAGE_PREFIX = 'manufacturer-';
    private const CONTENT_CSV_PATH = 'fixtures/manufacturers_content.csv';
    private const SLUG_COLUMN = 'brand_slug';

    private const DEFAULT_BRAND_DATA = [
        'background_image' => '',
        'background_image_mobile' => '',
        'logo' => '',
        'motto' => '',
        'description_part_1' => '',
        'description_part_2' => '',
        'featured_skus' => ''
    ];

    private const ALLOWED_IMAGE_EXTENSIONS = [
        'gif',
        'jpeg',
        'jpg',
        'png',
        'webp'
    ];

    public function __construct(
        private readonly OptionCollectionFactory $optionCollectionFactory,
        private readonly LoggerInterface $logger,
        private readonly StoreManagerInterface $storeManager,
        private readonly SlugHandler $slugHandler,
        private readonly ManufacturersConfig $manufacturersConfig,
        private readonly PageFactory $pageFactory,
        private readonly PageResource $pageResource,
        private readonly ManufacturerPageCollectionFactory $manufacturerPageCollectionFactory,
        private readonly ManufacturerPageRepositoryInterface $manufacturerPageRepository,
        private readonly ManufacturerPageInterfaceFactory $manufacturerPageFactory,
        private readonly ModuleReader $moduleReader,
        private readonly Csv $csvReader,
        private readonly Filesystem $filesystem,
        private readonly LockManagerInterface $lockManager,
        private readonly FeaturedProductsUpdater $featuredProductsUpdater
    ) {
    }

    public function generate(): void
    {
        if (!$this->lockManager->lock(self::LOCK_NAME, self::LOCK_TIMEOUT_SECONDS)) {
            throw new LocalizedException(
                __('Manufacturer page synchronization is already running.')
            );
        }

        try {
            $this->synchronize();
        } finally {
            $this->lockManager->unlock(self::LOCK_NAME);
        }
    }

    private function synchronize(): void
    {
        [$existingPages, $existingSlugs] = $this->getExistingMappings();

        $attributeId = $this->manufacturersConfig->getProductAttributeId();
        if ($attributeId <= 0) {
            throw new LocalizedException(
                __('The configured manufacturer product attribute does not exist.')
            );
        }

        $collection = $this->optionCollectionFactory->create()
            ->setAttributeFilter($attributeId)
            ->setStoreFilter($this->storeManager->getDefaultStoreView()->getId());

        $csvData = $this->readCsvData();
        $failures = 0;

        foreach ($collection as $option) {
            $optionId = (int)$option->getOptionId();
            $optionValue = trim((string)$option->getValue());

            if ($optionId <= 0
                || $optionValue === ''
                || !$this->manufacturersConfig->isManufacturerActive($optionId)
            ) {
                continue;
            }

            $mapping = $existingPages[$optionId] ?? null;

            try {
                $slug = $this->resolveSlug($mapping, $optionValue, $existingSlugs);
                $brandData = $this->getBrandData($csvData, $slug, $optionValue);

                $this->copyBrandImages($slug);
                $existingPages[$optionId] = $this->ensureManufacturerPage(
                    $mapping,
                    $optionId,
                    $optionValue,
                    $slug,
                    $brandData
                );

                if (!in_array($slug, $existingSlugs, true)) {
                    $existingSlugs[] = $slug;
                }
            } catch (\Throwable $exception) {
                $failures++;
                $this->logger->error(
                    sprintf(
                        'Autogedal_Manufacturers: Could not synchronize "%s": %s',
                        $optionValue,
                        $exception->getMessage()
                    ),
                    ['exception' => $exception]
                );
            }
        }

        if ($failures > 0) {
            throw new LocalizedException(
                __(
                    'Could not synchronize %1 manufacturer page(s). Check the logs for details.',
                    $failures
                )
            );
        }
    }

    private function getExistingMappings(): array
    {
        $existingPages = [];
        $existingSlugs = [];
        $slugOwners = [];
        $duplicatesDetected = false;

        foreach ($this->manufacturerPageCollectionFactory->create() as $page) {
            $optionId = (int)$page->getOptionId();
            if ($optionId <= 0) {
                continue;
            }

            if (isset($existingPages[$optionId])) {
                $this->logger->error(
                    sprintf(
                        'Autogedal_Manufacturers: Duplicate mapping for option ID %d.',
                        $optionId
                    )
                );
                $duplicatesDetected = true;
                continue;
            }

            $existingPages[$optionId] = $page;
            $slug = trim((string)$page->getSlug());
            if ($slug === '') {
                continue;
            }

            if (isset($slugOwners[$slug])) {
                $this->logger->error(
                    sprintf(
                        'Autogedal_Manufacturers: Duplicate URL key "%s" for option IDs %d and %d.',
                        $slug,
                        $slugOwners[$slug],
                        $optionId
                    )
                );
                $duplicatesDetected = true;
            }

            $slugOwners[$slug] = $optionId;
            $existingSlugs[] = $slug;
        }

        if ($duplicatesDetected) {
            throw new LocalizedException(
                __('Duplicate manufacturer mappings were found. Check the logs for details.')
            );
        }

        return [$existingPages, $existingSlugs];
    }

    private function resolveSlug(
        ?ManufacturerPageInterface $mapping,
        string $optionValue,
        array $existingSlugs
    ): string {
        $existingSlug = trim((string)$mapping?->getSlug());
        if ($existingSlug !== '') {
            return $existingSlug;
        }

        $baseSlug = $this->slugHandler->generateSlug($optionValue);
        if ($baseSlug === '') {
            throw new LocalizedException(
                __('Could not generate a valid URL key for manufacturer "%1".', $optionValue)
            );
        }

        return $this->slugHandler->makeSlugUnique($baseSlug, $existingSlugs);
    }

    private function getBrandData(array $csvData, string $slug, string $optionValue): array
    {
        if (!isset($csvData[$slug])) {
            $this->logger->warning(
                sprintf(
                    'Autogedal_Manufacturers: No fixture content configured for "%s" (%s).',
                    $optionValue,
                    $slug
                )
            );
        }

        return array_merge(self::DEFAULT_BRAND_DATA, $csvData[$slug] ?? []);
    }

    private function ensureManufacturerPage(
        ?ManufacturerPageInterface $mapping,
        int $optionId,
        string $optionValue,
        string $slug,
        array $brandData
    ): ManufacturerPageInterface {
        $cmsReference = trim((string)$mapping?->getCmsPage());
        $cmsPageIdentifier = $cmsReference !== ''
            ? $cmsReference
            : self::CMS_PAGE_PREFIX . $slug;

        $cmsPage = $this->pageFactory->create();
        $this->pageResource->load($cmsPage, $cmsPageIdentifier);

        $connection = $this->pageResource->getConnection();
        $connection->beginTransaction();

        try {
            if (!$cmsPage->getId()) {
                $cmsPage = $this->createCmsPage($cmsPageIdentifier, $optionId, $optionValue, $brandData);
            }

            $actualIdentifier = trim((string)$cmsPage->getIdentifier());
            if ($actualIdentifier === '') {
                throw new LocalizedException(
                    __('The CMS page for manufacturer "%1" has no identifier.', $optionValue)
                );
            }

            $mapping ??= $this->manufacturerPageFactory->create();
            $mappingChanged = !(int)$mapping->getPageId()
                || (int)$mapping->getOptionId() !== $optionId
                || (string)$mapping->getSlug() !== $slug
                || (string)$mapping->getCmsPage() !== $actualIdentifier;

            $mapping->setOptionId($optionId);
            $mapping->setSlug($slug);
            $mapping->setCmsPage($actualIdentifier);
            if ($mappingChanged) {
                $this->manufacturerPageRepository->save($mapping);
            }

            $connection->commit();
        } catch (\Throwable $exception) {
            $connection->rollBack();
            throw $exception;
        }

        return $mapping;
    }

    private function createCmsPage(
        string $identifier,
        int $optionId,
        string $optionValue,
        array $brandData
    ): Page {
        $cmsPage = $this->pageFactory->create();
        $cmsPage->setTitle('Producator ' . $optionValue)
            ->setIdentifier($identifier)
            ->setIsActive(true)
            ->setPageLayout('1column')
            ->setContent($this->getDefaultContent($optionId, $brandData))
            ->setData('stores', [Store::DEFAULT_STORE_ID]);

        $this->pageResource->save($cmsPage);

        return $cmsPage;
    }

    private function readCsvData(): array
    {
        $moduleDirRead = $this->getModuleDirectory();
        if (!$moduleDirRead->isFile(self::CONTENT_CSV_PATH)) {
            throw new LocalizedException(
                __('The bundled manufacturer content CSV does not exist.')
            );
        }

        try {
            $this->csvReader->setLineLength(10000);
            $this->csvReader->setDelimiter(',');
            $parsedData = $this->csvReader->getData($moduleDirRead->getAbsolutePath(self::CONTENT_CSV_PATH));
            if ($parsedData === []) {
                return [];
            }

            $headers = array_shift($parsedData);
            $requiredHeaders = [...array_keys(self::DEFAULT_BRAND_DATA), self::SLUG_COLUMN];
            if (array_diff($requiredHeaders, $headers) !== []) {
                throw new LocalizedException(
                    __('The manufacturer content CSV is missing required columns.')
                );
            }

            $csvData = [];
            foreach ($parsedData as $row) {
                if (count($headers) !== count($row)) {
                    continue;
                }

                $combinedRow = array_combine($headers, $row);
                $slug = trim((string)$combinedRow[self::SLUG_COLUMN]);
                if ($slug !== '') {
                    $csvData[$slug] = $combinedRow;
                }
            }

            return $csvData;
        } catch (\Throwable $exception) {
            throw new LocalizedException(
                __('Could not read the manufacturer content CSV.'),
                $exception instanceof \Exception ? $exception : null
            );
        }
    }

    private function getDefaultContent(int $optionId, array $brandData): string
    {
        $content = <<<HTML
<div data-content-type="html" data-appearance="default" data-element="main">{{widget type="Autogedal\Manufacturers\Block\Widget\Hero" background_image="{$brandData['background_image']}" background_image_mobile="{$brandData['background_image_mobile']}" attribute_value="{$optionId}" logo="{$brandData['logo']}" motto="{$brandData['motto']}" height="500"}}
<div class="manufacturer-main-content">{{widget type="Autogedal\Manufacturers\Block\Widget\TopCategories" attribute_value="{$optionId}" limit="5"}} {{widget type="Autogedal\Manufacturers\Block\Widget\ManufacturerInfo" attribute_value="{$optionId}" about_text="{$brandData['description_part_1']}" trust_text="{$brandData['description_part_2']}"}} <div class="homepage-products featured">
<div id="featured_product">{{widget type="Magento\CatalogWidget\Block\Product\ProductsList" title="Top Products" show_pager="1" products_per_page="10" products_count="10" template="Autogedal_Manufacturers::widget/product_list.phtml" conditions_encoded="^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,`aggregator`:`all`,`value`:`1`,`new_child`:``^],`1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,`attribute`:`sku`,`operator`:`()`,`value`:`{$brandData['featured_skus']}`^]^]" page_var_name="plrgys"}}</div>
</div>
{{widget type="Magento\Cms\Block\Widget\Block" template="widget/static_block/default.phtml" block_id="manufacturers_common_block"}}</div>
</div>
HTML;

        return trim((string)$brandData['featured_skus']) === ''
            ? $this->featuredProductsUpdater->updateContent($content, $optionId)
            : $content;
    }

    private function copyBrandImages(string $slug): void
    {
        $moduleDirRead = $this->getModuleDirectory();
        $sourcePath = 'fixtures/manufacturer_landing_pages/images/' . $slug;

        if (!$moduleDirRead->isDirectory($sourcePath)) {
            return;
        }

        $mediaDirWrite = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA);
        $destPath = 'wysiwyg/manufacturer_landing_pages/images/' . $slug;
        $mediaDirWrite->create($destPath);

        foreach ($moduleDirRead->read($sourcePath) as $fileRelativePath) {
            if (!$moduleDirRead->isFile($fileRelativePath)) {
                continue;
            }

            $filename = basename($fileRelativePath);
            $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
            if (!in_array($extension, self::ALLOWED_IMAGE_EXTENSIONS, true)) {
                continue;
            }

            $destFilePath = $destPath . '/' . $filename;
            $fileContent = $moduleDirRead->readFile($fileRelativePath);

            if ($mediaDirWrite->isFile($destFilePath)
                && $mediaDirWrite->readFile($destFilePath) === $fileContent
            ) {
                continue;
            }

            $mediaDirWrite->writeFile($destFilePath, $fileContent);
        }
    }

    private function getModuleDirectory(): ReadInterface
    {
        return $this->filesystem->getDirectoryReadByPath(
            $this->moduleReader->getModuleDir('', 'Autogedal_Manufacturers')
        );
    }
}
