<?php
declare(strict_types=1);

namespace Autogedal\BlackFriday\Controller\Favorites;

use Autogedal\BlackFriday\Model\Config;
use Autogedal\BlackFriday\Model\ProductProvider;
use Autogedal\BlackFriday\Model\RateLimiter;
use Autogedal\BlackFriday\Model\SubscriberStorage;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator;
use Magento\Newsletter\Model\Subscriber;
use Magento\Newsletter\Model\SubscriberFactory;
use Magento\Store\Model\StoreManagerInterface;

/** Favourites changed after the subscription: replaced by the browser holding the token, otherwise only added. */
class Save implements HttpPostActionInterface
{
    private const MAX_FAVORITES = 300;

    public function __construct(
        private readonly RequestInterface $request,
        private readonly JsonFactory $jsonFactory,
        private readonly FormKeyValidator $formKeyValidator,
        private readonly SubscriberStorage $storage,
        private readonly ProductProvider $productProvider,
        private readonly Config $config,
        private readonly SubscriberFactory $newsletterSubscriberFactory,
        private readonly StoreManagerInterface $storeManager,
        private readonly RateLimiter $rateLimiter
    ) {
    }

    public function execute()
    {
        $result = $this->jsonFactory->create();
        if (!$this->config->isEnabled() || !$this->formKeyValidator->validate($this->request)) {
            return $result->setData(['success' => false]);
        }
        // the limit comes first: past it nothing else runs (no product lookup, no query), same generic answer
        if (!$this->rateLimiter->hit('favorites_ip_' . $this->rateLimiter->clientIp($this->request), 120, 600)) {
            return $result->setData(['success' => true]);
        }
        $email = (string)$this->request->getParam('email');
        $token = (string)$this->request->getParam('token');

        // Same answer for every request without a valid token of the email (unknown, unsubscribed or subscribed
        // address): the endpoint must not tell whether an address is subscribed. A browser holding a token of the
        // email (owner or device) learns that the email was unsubscribed meanwhile, to show the form again.
        $role = $this->storage->tokenRole($email, $token);
        if ($role !== null) {
            if ($this->isUnsubscribed($email)) {
                return $result->setData(['success' => true, 'resubscribe' => true]);
            }
            $skus = $this->request->getParam('favorites', []);
            $skus = is_array($skus) ? array_values(array_filter(array_map('strval', $skus), 'strlen')) : [];
            $skus = array_slice($this->productProvider->filterCampaignSkus($skus), 0, self::MAX_FAVORITES);
            $this->storage->updateFavorites($email, $token, $skus);   // owner: replaces, device: adds
        }
        return $result->setData(['success' => true]);
    }

    /** Unsubscribed from the newsletter after subscribing here (email link, homepage, admin). */
    private function isUnsubscribed(string $email): bool
    {
        $websiteId = (int)$this->storeManager->getStore()->getWebsiteId();
        $subscriber = $this->newsletterSubscriberFactory->create()->loadBySubscriberEmail(trim($email), $websiteId);
        return !$subscriber->getId() || (int)$subscriber->getSubscriberStatus() === Subscriber::STATUS_UNSUBSCRIBED;
    }
}
