<?php

declare(strict_types=1);

namespace Ddeboer\Imap;

use Ddeboer\Imap\Exception\AuthenticationFailedException;
use Ddeboer\Imap\Exception\ResourceCheckFailureException;

/**
 * An IMAP server.
 */
final class Server implements ServerInterface
{
    private string $hostname;
    private string $port;
    private string $flags;
    /**
     * @var mixed[]
     */
    private array $parameters;
    private int $options;
    private int $retries;

    /**
     * Constructor.
     *
     * @param string  $hostname   Internet domain name or bracketed IP address
     *                            of server
     * @param string  $port       TCP port number
     * @param string  $flags      Optional flags
     * @param mixed[] $parameters Connection parameters
     * @param int     $options    Connection options
     * @param int     $retries    Retries number
     */
    public function __construct(
        string $hostname,
        string $port = '993',
        string $flags = '/imap/ssl/validate-cert',
        array $parameters = [],
        int $options = 0,
        int $retries = 1
    ) {
        if (!\function_exists('imap_open')) {
            throw new \RuntimeException('IMAP extension must be enabled');
        }

        $this->hostname   = $hostname;
        $this->port       = $port;
        $this->flags      = '' !== $flags ? '/' . \ltrim($flags, '/') : '';
        $this->parameters = $parameters;
        $this->options    = $options;
        $this->retries    = $retries;
    }

    /**
     * Authenticate connection.
     *
     * @param string $username Username
     * @param string $password Password
     *
     * @throws AuthenticationFailedException
     */
    public function authenticate(string $username, string $password): ConnectionInterface
    {
        $errorMessage = null;
        $errorNumber  = 0;
        \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool {
            $errorMessage = $message;
            $errorNumber  = $nr;

            return true;
        });

        $resource = \imap_open(
            $this->getServerString(),
            $username,
            $password,
            $this->options,
            $this->retries,
            $this->parameters
        );

        \restore_error_handler();

        if (false === $resource || null !== $errorMessage) {
            throw new AuthenticationFailedException(\sprintf(
                'Authentication failed for user "%s"%s',
                $username,
                null !== $errorMessage ? ': ' . $errorMessage : ''
            ), $errorNumber);
        }

        $check = \imap_check($resource);

        if (false === $check) {
            throw new ResourceCheckFailureException('Resource check failure');
        }

        $mailbox       = $check->Mailbox;
        $connection    = $mailbox;
        $curlyPosition = \strpos($mailbox, '}');
        if (false !== $curlyPosition) {
            $connection = \substr($mailbox, 0, $curlyPosition + 1);
        }

        // These are necessary to get rid of PHP throwing IMAP errors
        \imap_errors();
        \imap_alerts();

        return new Connection(new ImapResource($resource), $connection);
    }

    /**
     * Glues hostname, port and flags and returns result.
     */
    private function getServerString(): string
    {
        return \sprintf(
            '{%s%s%s}',
            $this->hostname,
            '' !== $this->port ? ':' . $this->port : '',
            $this->flags
        );
    }
}
