<?php
declare(strict_types=1);

namespace Autogedal\BlackFriday\Controller\Subscribe;

use Autogedal\BlackFriday\Model\Config;
use Autogedal\BlackFriday\Model\NewsletterSubscriber;
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\Framework\Exception\LocalizedException;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;

/** Newsletter subscription from the landing page, with the favourites of the visitor (may be none). */
class Index implements HttpPostActionInterface
{
    private const MAX_FAVORITES = 300;
    private const LIMIT_PER_IP = 10;          // subscriptions per visitor ...
    private const LIMIT_PER_IP_WINDOW = 600;  // ... in 10 minutes
    private const LIMIT_PER_EMAIL = 3;        // subscriptions of the same address ...
    private const LIMIT_PER_EMAIL_WINDOW = 3600;  // ... in one hour (email bombing)

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

    public function execute()
    {
        $result = $this->jsonFactory->create();
        if (!$this->config->isEnabled() || !$this->formKeyValidator->validate($this->request)) {
            return $result->setData(['success' => false, 'message' => (string)__('Sesiunea a expirat. Reincarca pagina si incearca din nou.')]);
        }
        $email = trim((string)$this->request->getParam('email'));
        if (!$this->request->getParam('consent')) {
            return $result->setData(['success' => false, 'message' => (string)__('Bifeaza acordul ca sa te poti abona.')]);
        }
        $ip = $this->rateLimiter->clientIp($this->request);
        if (!$this->rateLimiter->hit('subscribe_ip_' . $ip, self::LIMIT_PER_IP, self::LIMIT_PER_IP_WINDOW)
            || !$this->rateLimiter->hit('subscribe_email_' . mb_strtolower($email), self::LIMIT_PER_EMAIL, self::LIMIT_PER_EMAIL_WINDOW)
        ) {
            return $result->setData(['success' => false, 'message' => (string)__('Prea multe incercari. Te rugam sa incerci din nou peste cateva minute.')]);
        }
        try {
            $message = $this->newsletterSubscriber->subscribe($email);
            $skus = array_slice($this->productProvider->filterCampaignSkus($this->skus()), 0, self::MAX_FAVORITES);
            $row = $this->storage->subscribe(
                $email,
                $skus,
                (int)$this->storeManager->getStore()->getId(),
                $this->text('utm_source'),
                $this->text('utm_campaign')
            );
            return $result->setData([
                'success' => true,
                'message' => $message,
                'email' => (string)$row['email'],
                'token' => (string)$row['token'],
                'count' => count($skus),
            ]);
        } catch (LocalizedException $e) {
            return $result->setData(['success' => false, 'message' => $e->getMessage()]);
        } catch (\Throwable $e) {
            $this->logger->error('Black Friday subscribe: ' . $e->getMessage());
            return $result->setData(['success' => false, 'message' => (string)__('Something went wrong with the subscription.')]);
        }
    }

    /** @return string[] */
    private function skus(): array
    {
        $skus = $this->request->getParam('favorites', []);
        return is_array($skus) ? array_values(array_filter(array_map('strval', $skus), 'strlen')) : [];
    }

    /** UTM value: letters, digits, space, _ . - only, no leading = + - @ (no formula when the grid export is opened in Excel) */
    private function text(string $param): ?string
    {
        $value = (string)preg_replace('/[^\p{L}\p{N} _.\-]+/u', '', (string)$this->request->getParam($param));
        $value = trim(ltrim($value, '=+-@ '));
        return $value !== '' ? mb_substr($value, 0, 255) : null;
    }
}
