<?php

/**
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * https://www.d3data.de
 *
 * @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
 * @author    D3 Data Development - Daniel Seifert <info@shopmodule.com>
 * @link      https://www.oxidmodule.com
 */

declare(strict_types=1);

namespace D3\MailConfigChecker\Application\Controller\Admin;

use Assert\Assert;
use Assert\InvalidArgumentException;
use D3\MailConfigChecker\Application\Model\Constants;
use Net_SMTP;
use OxidEsales\Eshop\Application\Controller\Admin\AdminDetailsController;
use OxidEsales\Eshop\Application\Model\Shop;
use OxidEsales\Eshop\Core\Registry;
use PEAR;
use PEAR_Error;

class SmtpChecker extends AdminDetailsController
{
    protected bool $debug = true;

    protected string $host;
    protected int $port;
    protected string $user;
    protected string $pwd;
    protected string $from;
    protected ?string $to = null;

    protected Net_SMTP $smtp;
    protected string $action = '';
    protected array $log = [];

    public function __construct()
    {
        parent::__construct();

        $this->setSmtpCredentials();
        $this->from = Registry::getRequest()->getRequestEscapedParameter('from') ?: '';
        $this->addTplParam('from', $this->from);
        $this->to = Registry::getRequest()->getRequestEscapedParameter('to') ?: '';
        $this->addTplParam('recipient', $this->to);
        $this->addTplParam('sendMail', Registry::getRequest()->getRequestEscapedParameter('sendmail'));

        $this->addTplParam('smtpLog', $this->log);
    }

    protected function setSmtpCredentials(): void
    {
        $activeShop = Registry::getConfig()->getActiveShop();

        if ($localHost = Registry::getRequest()->getRequestEscapedParameter('smtpHost')) {
            ['host' => $shopHost, 'port' => $shopPort] = parse_url(trim($localHost));
        } else {
            ['host' => $shopHost, 'port' => $shopPort] = parse_url(trim($activeShop->getFieldData('oxsmtp')));
        }

        $this->host = $shopHost ?? '';
        $this->addTplParam('smtpHost', Registry::getRequest()->getRequestEscapedParameter('smtpHost'));
        $this->port = $shopPort ?? 587;
        $this->user = Registry::getRequest()->getRequestEscapedParameter('smtpUser') ?: $activeShop->getFieldData('oxsmtpuser');
        $this->addTplParam('smtpUser', $this->user);
        $this->pwd  = Registry::getRequest()->getRequestEscapedParameter('smtpPwd') ?: $activeShop->getFieldData('oxsmtppwd');
        $this->addTplParam('smtpPwd', $this->pwd);
    }

    public function getTemplateName(): string
    {
        return '@'.Constants::OXID_MODULE_ID.'/admin/smtpCheck';
    }

    public function render(): ?string
    {
        $this->addTplParam('shop', Registry::getConfig()->getActiveShop());

        return parent::render();
    }

    public function getMailAddressList(): array
    {
        /** @var Shop $shop */
        $shop = Registry::getConfig()->getActiveShop();

        return array_filter(
            array_unique(
                [
                    $shop->getFieldData('oxinfoemail'),
                    $shop->getFieldData('oxorderemail'),
                    $shop->getFieldData('oxowneremail'),
                ]
            )
        );
    }

    public function sendMail(): void
    {
        try {
            $this->hostIsAvailable();
            $this->connect();
            $this->auth();
            $this->setFrom();
            $this->setRecipient();
            $this->sendContent();
            $this->addTplParam('success', true);
        } catch (InvalidArgumentException $e) {
            Registry::getUtilsView()->addErrorToDisplay($e);
        } finally {
            $this->disconnect();
        }

        $this->addTplParam('smtpLog', $this->log);
    }

    /**
     * @throws InvalidArgumentException
     * @return void
     */
    protected function hostIsAvailable(): void
    {
        $this->action = __FUNCTION__;
        Assert::that(
            ($this->smtp = new Net_SMTP($this->host, $this->port, $_SERVER['HTTP_HOST'])),
            Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_INSTANCE')
        )->isInstanceOf(
            Net_SMTP::class,
            Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_NOCONNECTION')
        );
        $this->smtp->setDebug($this->debug, [$this, 'dumpDebug']);
    }

    protected function connect(): void
    {
        $this->action = __FUNCTION__;
        Assert::that(
            $this->smtp->connect(),
            Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_CONNECTION')
        )->notIsInstanceOf(
            PEAR_Error::class,
            [$this, 'formatMessage'],
            Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_NOCONNECTION')
        );
    }

    /**
     * @throws InvalidArgumentException
     * @return void
     */
    protected function auth(): void
    {
        $this->action = __FUNCTION__;
        Assert::that(
            $this->smtp->auth($this->user, $this->pwd),
            Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_AUTHENTICATION')
        )->notIsInstanceOf(
            PEAR_Error::class,
            [$this, 'formatMessage'],
            Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_NOAUTHENTICATION')
        );
    }

    protected function setFrom(): void
    {
        $this->action = __FUNCTION__;
        Assert::that(
            $this->smtp->mailFrom($this->from),
            Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_SENDER')
        )->notIsInstanceOf(
            PEAR_Error::class,
            [$this, 'formatMessage'],
            sprintf(Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_NOSENDER'), $this->from)
        );
    }

    protected function setRecipient(): void
    {
        if (!$this->to) {
            return;
        }

        $this->action = __FUNCTION__;
        Assert::that(
            $this->smtp->rcptTo($this->to),
            Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_RECIPIENT')
        )->notIsInstanceOf(
            PEAR_Error::class,
            [$this, 'formatMessage'],
            sprintf(Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_NORECIPIENT'), $this->to)
        );
    }

    protected function sendContent(): void
    {
        if (!Registry::getRequest()->getRequestEscapedParameter('sendmail')) {
            return;
        }

        $this->action = __FUNCTION__;
        $subj = "Subject: Test Message\n";
        $body = "Body Line 1\nBody Line 2";
        Assert::that(
            $this->smtp->data($subj . "\r\n" . $body),
            Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_TRANSMIT')
        )->notIsInstanceOf(
            PEAR_Error::class,
            [$this, 'formatMessage'],
            Registry::getLang()->translateString('D3_MAILCHECKER_SMTPCHECK_NOTRANSMIT')
        );
    }

    protected function disconnect(): void
    {
        $this->action = __FUNCTION__;
        $this->smtp->disconnect();
    }

    public function dumpDebug($smtp, $message): void
    {
        unset($smtp);

        if (!isset($this->log[$this->action])) {
            $this->log[$this->action] = [];
        }
        $this->log[$this->action][] = trim(htmlentities($message));
    }

    public function formatMessage(array $arguments): ?string
    {
        /** @var PEAR_Error|true $response */
        $response = $arguments['value'];
        $propertyPath = $arguments['propertyPath'];
        if (PEAR::isError($response)) {
            return ($propertyPath ? $propertyPath .' - ' : '').
                   $response->getMessage() .
                   ($response->getCode() ? ': '.$response->getCode() : '');
        }

        return null;
    }
}