<?php

/**
 * Created by PhpStorm.
 * User: d.lanec
 * Date: 19.05.2020
 * Time: 21:30
 */

namespace Mailbox\Infrastructure\ProtocolAdapter;

use Mailbox\Application\Config;
use Mailbox\Infrastructure\ImapAdapter\ImapProtocol;
use Mailbox\Infrastructure\ImapAdapter\ImapStorage;
use Mailbox\Infrastructure\Repositories\Helpers\SocketTimeoutTrait;
use Mailbox\Infrastructure\Repositories\ProtocolConnectionInterface;
use Mailbox\Infrastructure\Repositories\ProtocolInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
use Zend\Mail\Exception\ExceptionInterface;

class ZendProtocol implements ProtocolInterface
{
    use LoggerAwareTrait;
    use SocketTimeoutTrait;

    protected const TIMEOUT = 15;


    /**
     * адаптер
     * @var ImapStorage
     */
    protected $adapter;

    /**
     * @var Config
     */
    protected $config;

    /**
     * @var array
     */
    private $uids = [];

    private $countMsg;

    /**
     * ZendProtocol constructor.
     * @param ProtocolConnectionInterface $config
     */
    public function __construct(ProtocolConnectionInterface $config)
    {

        $this->config = $config;
        $this->logger = new NullLogger();

        $this->setTimeout(self::TIMEOUT);

        //@todo криво работает strpos
//        if ((bool)ini_get('mbstring.func_overload') === true) {
//         //   mb_internal_encoding('8bit');
//        }
    }

    /**
     * получение драйвера для работы с почтой, установка подключения
     * @param bool $rebuild
     * @return ImapStorage
     */
    protected function getAdapter($rebuild = false): ImapStorage
    {

        if ($rebuild === false && is_a($this->adapter, ImapStorage::class)) {
            return $this->adapter;
        }

        if ($rebuild === true && is_a($this->adapter, ImapStorage::class)) {
            $this->adapter->close();
        }

        try {
            $protocol = new ImapProtocol(
                $this->config->getHost(),
                $this->config->getPort(),
                $this->config->isSsl() === true ? 'SSL' : false
            );

            if ($this->config->isSocketDebug() === true) {
                $protocol->setLogger($this->logger);
            }

        } catch (ExceptionInterface $e) {
            throw new ProtocolException('Connection error: ' . $e->getMessage());
        }

        $this->logger->debug('Успешное подключение к серверу');

        if (!$protocol->login($this->config->getLogin(), $this->config->getPassword())) {
            throw new ProtocolException('Cannot login, user or password wrong.');
        }

        $this->logger->debug('Успешная авторизация на сервере');

        $this->adapter = new ImapStorage($protocol);
        $this->adapter->setLogger($this->logger);

        return $this->adapter;
    }

    /**
     * @todo пока не используется , подумать кака встроить на уровень сокет комманд
     * @param string $message
     */
    protected function debugCommand(string $message)
    {
        if ($this->config->isSocketDebug() === true) {
            $this->logger->debug($message);
        }
    }

    public function getRawHeaderById(int $id): string
    {

        $this->debugCommand('RFC822 GET HEADER by id ' . $id);

        if (!$raw = $this->getAdapter()->getProtocol()->fetch('RFC822.HEADER', $id)) {
            throw new ProtocolException('raw header not found by message id ' . $id);
        }

        return $raw;
    }

    /**
     * @param int $id порядковый номер
     * @return string
     */
    public function getRawContentById(int $id): string
    {
        $this->debugCommand('RFC822 GET CONTENT by id ' . $id);

        if (!$raw = $this->getAdapter()->getProtocol()->fetch('RFC822', $id)) {
            throw new ProtocolException('raw content not found by message id ' . $id);
        }

        return $raw;
    }

    /**
     * @param int $uid уникальный ключ письма на сервере
     * @return string
     */
    public function getRawContentByUid(int $uid): string
    {
        $this->debugCommand('RFC822 GET RAW CONTENT by uuid ' . $uid);

        if (!$raw = $this->getAdapter()->getProtocol()->fetch('RFC822', $uid, null, true)) {
            throw new ProtocolException('raw content not found by message uid ' . $uid);
        }

        return $raw;
    }

    /**
     * получение уникальноего ключа письма на сервере
     * @param int $id
     * @return int
     */
    public function getUidById(int $id): int
    {
        if (isset($this->uids[$id])) {
            return $this->uids[$id];
        }

        $this->debugCommand('RFC822 GET UID by id' . $id);

        $uid = $this->getAdapter()->getUniqueId($id);

        $this->uids[$id] = $uid;

        return $uid;
    }

    /**
     * @return string
     */
    public function getCurrentFolder(): string
    {
        return $this->getAdapter()->getCurrentFolder();
    }

    /**
     * @param string $folder
     * @return array
     */
    public function selectFolder(string $folder): array
    {
        $select = $this->getAdapter(true)->selectFolder($folder);
        if (isset($select['exists'])) {
            $this->countMsg = (int) $select['exists'];
        }
        return $select;
    }

    /**
     * @param null $rootFolder
     * @return array
     */
    public function getFolders($rootFolder = null): array
    {
        return $this->getAdapter()->getProtocol()->listMailbox((string)$rootFolder);
    }

    /**
     * @param int $messageNumber
     * @param array $flags
     */
    public function setFlags(int $messageNumber, array $flags): void
    {
        $this->getAdapter()->setFlags($messageNumber, $flags);
    }

    /**
     * @param int $messageNumber
     * @param array $flags
     */
    public function unsetFlags(int $messageNumber, array $flags): void
    {
        $this->getAdapter()->unsetFlags($messageNumber, $flags);
    }

    /**
     * @return int
     */
    public function countMessages(): int
    {
        if ($this->countMsg !== null) {
            return $this->countMsg;
        }

        $examine = $this->getAdapter()->examineFolder($this->getCurrentFolder());

        if ($examine && isset($examine['exists'])) {
            $this->countMsg = (int) $examine['exists'];
            return $this->countMsg;
        }

        return (int)$this->getAdapter()->countMessages();
    }

    /**
     *
     */
    public function noop(): void
    {
        $this->getAdapter()->noop();
    }

    /**
     * @param int $id
     * @param string $folder
     */
    public function moveMessage(int $id, string $folder): void
    {
        $this->getAdapter()->moveMessage($id, $folder);
    }

    /**
     *
     */
    public function __destruct()
    {
        $this->setDefaultTimeout();
    }
}
