<?php
declare(strict_types=1);

namespace Autogedal\Manufacturers\Controller\Adminhtml\Page;

use Autogedal\Manufacturers\Api\Data\ManufacturerPageInterface;
use Autogedal\Manufacturers\Api\Data\ManufacturerPageInterfaceFactory;
use Autogedal\Manufacturers\Api\ManufacturerPageRepositoryInterface;
use Autogedal\Manufacturers\Controller\Adminhtml\AbstractPage;
use Autogedal\Manufacturers\Model\Config as ManufacturersConfig;
use Autogedal\Manufacturers\Service\ManufacturerGenerator;
use Autogedal\Manufacturers\Service\SlugHandler;
use Magento\Backend\App\Action\Context;
use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Config as EavConfig;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Lock\LockManagerInterface;
use Psr\Log\LoggerInterface;

class Save extends AbstractPage implements HttpPostActionInterface
{
    public function __construct(
        Context $context,
        private readonly ManufacturerPageRepositoryInterface $pageRepository,
        private readonly ManufacturerPageInterfaceFactory $pageFactory,
        private readonly EavConfig $eavConfig,
        private readonly ManufacturersConfig $manufacturersConfig,
        private readonly SlugHandler $slugHandler,
        private readonly LockManagerInterface $lockManager,
        private readonly LoggerInterface $logger
    ) {
        parent::__construct($context);
    }

    public function execute(): ResultInterface
    {
        $id = (int)$this->getRequest()->getParam('page_id');
        $resultRedirect = $this->resultRedirectFactory->create();
        $lockAcquired = false;

        try {
            $lockAcquired = $this->lockManager->lock(
                ManufacturerGenerator::LOCK_NAME,
                ManufacturerGenerator::LOCK_TIMEOUT_SECONDS
            );
            if (!$lockAcquired) {
                throw new LocalizedException(
                    __('Manufacturer page synchronization is already running. Please try again.')
                );
            }

            $page = $this->savePage($id);
            $this->messageManager->addSuccessMessage(__('The manufacturer page has been saved.'));

            return $this->getRequest()->getParam('back')
                ? $resultRedirect->setPath('*/*/edit', ['id' => $page->getPageId()])
                : $resultRedirect->setPath('*/*/');
        } catch (LocalizedException $exception) {
            $this->messageManager->addErrorMessage($exception->getMessage());
        } catch (\Throwable $exception) {
            $this->logger->error(
                'Could not save manufacturer page mapping: ' . $exception->getMessage(),
                ['exception' => $exception]
            );
            $this->messageManager->addErrorMessage(__('Something went wrong while saving the manufacturer page.'));
        } finally {
            if ($lockAcquired) {
                $this->lockManager->unlock(ManufacturerGenerator::LOCK_NAME);
            }
        }

        return $resultRedirect->setPath('*/*/edit', $id > 0 ? ['id' => $id] : []);
    }

    private function savePage(int $id): ManufacturerPageInterface
    {
        $data = $this->getRequest()->getPostValue();
        if (!is_array($data)) {
            throw new LocalizedException(__('No form data was submitted.'));
        }

        $optionId = (int)($data['option_id'] ?? 0);
        $cmsPageIdentifier = is_scalar($data['cms_page'] ?? null) ? trim((string)$data['cms_page']) : '';
        if ($optionId <= 0 || $cmsPageIdentifier === '') {
            throw new LocalizedException(__('Manufacturer and CMS page are required.'));
        }

        $page = $id > 0 ? $this->pageRepository->getById($id) : $this->pageFactory->create();
        $slug = $this->generateSlug($optionId);
        $this->assertMappingIsUnique((int)$page->getPageId(), $optionId, $slug);

        $page->setOptionId($optionId);
        $page->setCmsPage($cmsPageIdentifier);
        $page->setSlug($slug);
        $this->pageRepository->save($page);

        return $page;
    }

    private function generateSlug(int $optionId): string
    {
        $attribute = $this->eavConfig->getAttribute(
            Product::ENTITY,
            $this->manufacturersConfig->getProductAttribute()
        );
        $optionText = trim((string)$attribute->getSource()->getOptionText($optionId));
        if ($optionText === '') {
            throw new LocalizedException(__('The selected manufacturer option does not exist.'));
        }

        $slug = $this->slugHandler->generateSlug($optionText);
        if ($slug === '') {
            throw new LocalizedException(__('A valid URL key could not be generated for this manufacturer.'));
        }

        return $slug;
    }

    private function assertMappingIsUnique(int $pageId, int $optionId, string $slug): void
    {
        if ($this->belongsToAnotherPage(fn () => $this->pageRepository->getByOptionId($optionId), $pageId)) {
            throw new LocalizedException(__('This manufacturer already has a page mapping.'));
        }

        if ($this->belongsToAnotherPage(fn () => $this->pageRepository->getBySlug($slug), $pageId)) {
            throw new LocalizedException(__('Another manufacturer already uses this URL key.'));
        }
    }

    private function belongsToAnotherPage(callable $findPage, int $pageId): bool
    {
        try {
            return (int)$findPage()->getPageId() !== $pageId;
        } catch (NoSuchEntityException) {
            return false;
        }
    }
}
