<?php
declare(strict_types=1);

namespace Autogedal\BlackFriday\Model;

use Autogedal\BlackFriday\Block\Email\Items;
use Magento\Framework\App\Area;
use Magento\Framework\Lock\LockManagerInterface;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\BlockFactory;
use Magento\Newsletter\Model\Subscriber;
use Magento\Newsletter\Model\SubscriberFactory;
use Magento\Store\Model\App\Emulation;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;

/**
 * Launch email with the favourite products of each subscriber. Runs between the start and the end of the campaign,
 * in batches; every row is claimed before sending so a subscriber gets the email once.
 */
class LaunchMailer
{
    private const LOCK = 'autogedal_blackfriday_launch_mailer';

    public function __construct(
        private readonly Config $config,
        private readonly SubscriberStorage $storage,
        private readonly ProductProvider $productProvider,
        private readonly TransportBuilder $transportBuilder,
        private readonly Emulation $emulation,
        private readonly BlockFactory $blockFactory,
        private readonly StoreManagerInterface $storeManager,
        private readonly SubscriberFactory $newsletterSubscriberFactory,
        private readonly UrlInterface $urlBuilder,
        private readonly LockManagerInterface $lockManager,
        private readonly LoggerInterface $logger
    ) {
    }

    /** @return array{sent: int, errors: int, skipped: string} */
    public function sendPending(?int $limit = null): array
    {
        if (!$this->config->isEnabled() || !$this->config->isLive() || $this->config->isOver()) {
            return ['sent' => 0, 'errors' => 0, 'skipped' => 'campania nu este activa'];
        }
        if (!$this->lockManager->lock(self::LOCK, 0)) {
            return ['sent' => 0, 'errors' => 0, 'skipped' => 'alta rulare este in curs'];
        }
        $sent = $errors = 0;
        try {
            $this->storage->expireStuck(30);
            foreach ($this->storage->getPending($limit ?? $this->config->getEmailBatchSize()) as $row) {
                $id = (int)$row['entity_id'];
                if (!$this->storage->claim($id)) {
                    continue;
                }
                try {
                    $storeId = (int)$row['store_id'] ?: (int)$this->storeManager->getDefaultStoreView()->getId();
                    if ($this->isUnsubscribed((string)$row['email'], $storeId)) {
                        $this->storage->markSkipped($id, SubscriberStorage::REASON_UNSUBSCRIBED);
                        continue;
                    }
                    $skus = array_filter(explode(',', (string)$row['favorites']));
                    $this->send((string)$row['email'], $skus, $storeId);
                    $this->storage->markSent($id);
                    $sent++;
                } catch (\Throwable $e) {
                    $this->storage->markError($id, $e->getMessage());
                    $this->logger->error('Black Friday launch email: ' . $e->getMessage(), ['email' => $row['email']]);
                    $errors++;
                }
            }
        } finally {
            $this->lockManager->unlock(self::LOCK);
        }
        return ['sent' => $sent, 'errors' => $errors, 'skipped' => ''];
    }

    /**
     * Sends the launch email to an address with the given favourites (also used by the admin test email).
     *
     * @param string[] $skus
     */
    public function send(string $recipient, array $skus, int $storeId): void
    {
        $this->emulation->startEnvironmentEmulation($storeId, Area::AREA_FRONTEND, true);
        try {
            $items = $this->productProvider->getItemsForSkus($skus);
            // emulation does not change the app area (adminhtml / crontab), so the template area is set explicitly
            /** @var Items $block */
            $block = $this->blockFactory->createBlock(Items::class, ['data' => ['area' => Area::AREA_FRONTEND]])
                ->setTemplate('Autogedal_BlackFriday::email/items.phtml');
            $block->setItems($items);
            $vars = [
                'items_html' => $items ? $block->toHtml() : '',
                'has_items' => (bool)$items,
                'no_items' => !$items,
                'count' => count($items),
                'period' => mb_strtolower($this->config->getPeriodLabel()),
                'campaign_url' => $this->urlBuilder->getUrl('black-friday', ['_scope' => $storeId, '_nosid' => true]),
                'unsubscribe_url' => $this->unsubscribeUrl($recipient, $storeId),
            ];
            $transport = $this->transportBuilder
                ->setTemplateIdentifier($this->config->getEmailTemplate())
                ->setTemplateOptions(['area' => Area::AREA_FRONTEND, 'store' => $storeId])
                ->setTemplateVars($vars)
                ->setFromByScope($this->config->getEmailIdentity(), $storeId)
                ->addTo($recipient)
                ->getTransport();
            $transport->sendMessage();
        } finally {
            $this->emulation->stopEnvironmentEmulation();
        }
    }

    private function isUnsubscribed(string $email, int $storeId): bool
    {
        $websiteId = (int)$this->storeManager->getStore($storeId)->getWebsiteId();
        $subscriber = $this->newsletterSubscriberFactory->create()->loadBySubscriberEmail($email, $websiteId);
        return !$subscriber->getId() || (int)$subscriber->getSubscriberStatus() === Subscriber::STATUS_UNSUBSCRIBED;
    }

    private function unsubscribeUrl(string $email, int $storeId): string
    {
        $websiteId = (int)$this->storeManager->getStore($storeId)->getWebsiteId();
        $subscriber = $this->newsletterSubscriberFactory->create()->loadBySubscriberEmail($email, $websiteId);
        if (!$subscriber->getId()) {
            return '';
        }
        // own route: unsubscribes like the core link, then shows the confirmation on the campaign page
        return $this->urlBuilder->getUrl('black-friday/unsubscribe', [
            '_scope' => $storeId,
            '_nosid' => true,
            '_query' => ['id' => $subscriber->getId(), 'code' => $subscriber->getCode()],
        ]);
    }
}
