<?php

declare(strict_types=1);

namespace Autogedal\FeedMonitor\Model;

use Magento\Framework\App\Area;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\DataObject;
use Magento\Framework\Mail\Template\TransportBuilderFactory;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\Store;
use Psr\Log\LoggerInterface;

/**
 * Builds and sends the alert / reminder / recovery email (Magento TransportBuilder, i.e. the configured SMTP).
 */
class AlertNotifier
{
    private const HINTS = [
        Problem::FETCH => 'Verifica de pe server daca sursa raspunde (curl -sv URL) si daca furnizorul nu a blocat IP-ul serverului.',
        Problem::INVALID => 'Furnizorul a schimbat formatul sau a trimis o pagina de eroare in locul feed-ului. Detalii in logul jobului.',
        Problem::EMPTY => 'Furnizorul a trimis un feed fara produse (posibil in curs de regenerare). Stocul si preturile au ramas neschimbate.',
        Problem::INCOMPLETE => 'Stocul si preturile au ramas neschimbate. Daca scaderea e reala (furnizorul a scos produse), permite o rulare: bin/magento autogedal:feed-monitor:allow-incomplete %job%',
        Problem::EXCEPTION => 'Vezi mesajul de mai sus, logul jobului si cron_schedule.',
        Problem::IMPORT => 'Vezi erorile de mai sus si logul complet al importului; jobul se poate rula manual din System > Import Jobs.',
        Problem::UPDATE => 'Feed-ul a fost citit, dar salvarea a esuat pentru produsele din mesaj; restul s-au actualizat. Lista completa si erorile sunt in logul jobului.',
        Problem::STUCK => 'Procesul s-a oprit inainte de final (eroare fatala, memorie insuficienta, proces oprit). Vezi var/log/exception.log si logul jobului.',
        Problem::STALE => 'Verifica in cron_schedule daca jobul este programat si daca grupul lui de cron ruleaza.',
        Problem::CRON_DOWN => 'Verifica crontab-ul serverului (bin/magento cron:run) si procesele PHP blocate.',
    ];

    /** Firebear imports have no guard: the import already ran with the feed when the alert goes. */
    private const FIREBEAR_HINTS = [
        Problem::INVALID => 'Importul Firebear a rulat cu acest feed. Verifica feed-ul furnizorului si produsele importate (erorile complete in logul importului).',
        Problem::EMPTY => 'Importul Firebear a rulat cu un feed fara produse. Verifica sursa jobului in System > Import Jobs.',
        Problem::INCOMPLETE => 'Importul Firebear a rulat cu acest feed. Daca scaderea e reala (furnizorul a scos produse), accepta noul numar: bin/magento autogedal:feed-monitor:allow-incomplete %job%',
    ];

    private const TITLES = [
        AlertManager::TYPE_ALERT => 'ALERTA',
        AlertManager::TYPE_REMINDER => 'REAMINTIRE',
        AlertManager::TYPE_RECOVERY => 'REZOLVAT',
    ];

    public function __construct(
        private readonly Config $config,
        private readonly JobRegistry $jobs,
        private readonly RunRepository $runs,
        private readonly TransportBuilderFactory $transportBuilderFactory,
        private readonly ScopeConfigInterface $scopeConfig,
        private readonly TimezoneInterface $timezone,
        private readonly Clock $clock,
        private readonly LoggerInterface $logger
    ) {
    }

    /**
     * @param string $since UTC date when the problem was first detected
     * @return bool true when the email left
     */
    public function send(string $type, string $key, string $problem, string $message, string $since): bool
    {
        if (!$this->config->isEmailEnabled()) {
            return false;
        }

        [$jobCode, $feedCode] = MonitorKey::split($key);
        $label = $key === MonitorKey::MAGENTO_CRON ? 'Cron Magento' : $this->jobs->getLabel($jobCode, $feedCode);
        $site = rtrim((string)$this->scopeConfig->getValue('web/unsecure/base_url', ScopeInterface::SCOPE_STORE, Store::DEFAULT_STORE_ID), '/');
        $title = self::TITLES[$type] ?? strtoupper($type);
        $lastSuccess = $key === MonitorKey::MAGENTO_CRON ? null : $this->runs->getLastSuccessful($jobCode, $feedCode);

        $alert = new DataObject([
            'subject' => sprintf('[%s] %s feed: %s - %s', $site, $title, $label, Problem::label($problem)),
            'title' => $title,
            'is_recovery' => $type === AlertManager::TYPE_RECOVERY,
            'label' => $label,
            'monitor_key' => $key,
            'problem' => Problem::label($problem),
            'message' => $message,
            'since' => $since !== '' ? $this->local($since) : '',
            'duration' => $since !== '' ? $this->duration($since) : '',
            'last_success' => $lastSuccess
                ? $this->local((string)($lastSuccess['finished_at'] ?? $lastSuccess['started_at']))
                    . ' (' . (int)$lastSuccess['items_read'] . ' produse citite)'
                : 'nicio rulare reusita inregistrata',
            'log_file' => $this->jobs->getLogFile($jobCode),
            'hint' => $type === AlertManager::TYPE_RECOVERY
                ? ''
                : str_replace(
                    '%job%',
                    trim($jobCode . ($feedCode !== '' ? ' --feed=' . $feedCode : '')),
                    (str_starts_with($jobCode, 'firebear_job_') ? self::FIREBEAR_HINTS[$problem] ?? null : null) ?? self::HINTS[$problem] ?? ''
                ),
            'site' => $site,
        ]);

        $recipients = $this->config->getRecipients();
        try {
            // a new builder per email: a failed send must not leave recipients behind for the next one
            $builder = $this->transportBuilderFactory->create();
            $builder
                ->setTemplateIdentifier($this->config->getEmailTemplate())
                ->setTemplateOptions(['area' => Area::AREA_FRONTEND, 'store' => Store::DEFAULT_STORE_ID])
                ->setTemplateVars(['alert' => $alert])
                ->setFromByScope($this->config->getSenderIdentity(), Store::DEFAULT_STORE_ID);
            if (!$recipients) {
                $recipients = [(string)$this->scopeConfig->getValue(
                    'trans_email/ident_' . $this->config->getSenderIdentity() . '/email',
                    ScopeInterface::SCOPE_STORE,
                    Store::DEFAULT_STORE_ID
                )];
            }
            foreach (array_filter($recipients) as $recipient) {
                $builder->addTo($recipient);
            }
            $builder->getTransport()->sendMessage();

            return true;
        } catch (\Throwable $e) {
            $this->logger->error('Feed monitor: email not sent for ' . $key . ': ' . $e->getMessage(), ['exception' => $e]);

            return false;
        }
    }

    private function local(string $utc): string
    {
        return $this->timezone->date(new \DateTime($utc, new \DateTimeZone('UTC')))->format('d.m.Y H:i');
    }

    private function duration(string $sinceUtc): string
    {
        $minutes = max(0, intdiv($this->clock->now() - (int)strtotime($sinceUtc . ' UTC'), 60));
        if ($minutes < 60) {
            return $minutes . ' min';
        }
        $hours = intdiv($minutes, 60);

        return $hours < 48 ? $hours . ' h ' . ($minutes % 60) . ' min' : intdiv($hours, 24) . ' zile ' . ($hours % 24) . ' h';
    }
}
