<?php
declare(strict_types=1);

namespace App\Services;

final class SmtpClient
{
    private string $host;
    private int $port;
    private string $secure; // 'ssl' or 'tls' or ''
    private string $user;
    private string $pass;
    private int $timeout;

    public function __construct(string $host, int $port, string $secure, string $user, string $pass, int $timeout = 20)
    {
        $this->host = $host;
        $this->port = $port;
        $this->secure = $secure;
        $this->user = $user;
        $this->pass = $pass;
        $this->timeout = $timeout;
    }

    public function send(string $fromEmail, string $fromName, string $toEmail, string $toName, string $subject, string $html, string $text): void
    {
        $remote = ($this->secure === 'ssl') ? "ssl://{$this->host}:{$this->port}" : "{$this->host}:{$this->port}";
        $fp = @stream_socket_client($remote, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT);
        if (!$fp) throw new \RuntimeException("SMTP connect failed: $errstr ($errno)");

        stream_set_timeout($fp, $this->timeout);

        $this->expect($fp, [220]);

        $this->cmd($fp, "EHLO easychair.local", [250]);

        if ($this->secure === 'tls') {
            $this->cmd($fp, "STARTTLS", [220]);
            if (!stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                throw new \RuntimeException("STARTTLS failed");
            }
            $this->cmd($fp, "EHLO easychair.local", [250]);
        }

        // AUTH LOGIN
        $this->cmd($fp, "AUTH LOGIN", [334]);
        $this->cmd($fp, base64_encode($this->user), [334]);
        $this->cmd($fp, base64_encode($this->pass), [235]);

        $this->cmd($fp, "MAIL FROM:<{$fromEmail}>", [250]);
        $this->cmd($fp, "RCPT TO:<{$toEmail}>", [250, 251]);
        $this->cmd($fp, "DATA", [354]);

        $boundary = "=_ec_" . bin2hex(random_bytes(8));
        $fromHeader = $this->encodeName($fromName) . " <{$fromEmail}>";
        $toHeader = ($toName !== '' ? $this->encodeName($toName) . " <{$toEmail}>" : $toEmail);

        $headers = [];
        $headers[] = "From: {$fromHeader}";
        $headers[] = "To: {$toHeader}";
        $headers[] = "Subject: " . $this->encodeSubject($subject);
        $headers[] = "MIME-Version: 1.0";
        $headers[] = "Content-Type: multipart/alternative; boundary=\"{$boundary}\"";
        $headers[] = "Date: " . date('r');
        $headers[] = "Message-ID: <" . bin2hex(random_bytes(16)) . "@easychair.local>";

        $body  = implode("\r\n", $headers) . "\r\n\r\n";
        $body .= "--{$boundary}\r\n";
        $body .= "Content-Type: text/plain; charset=UTF-8\r\n";
        $body .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
        $body .= ($text !== '' ? $text : strip_tags($html)) . "\r\n\r\n";
        $body .= "--{$boundary}\r\n";
        $body .= "Content-Type: text/html; charset=UTF-8\r\n";
        $body .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
        $body .= ($html !== '' ? $html : nl2br(htmlspecialchars($text, ENT_QUOTES, 'UTF-8'))) . "\r\n\r\n";
        $body .= "--{$boundary}--\r\n";

        // End DATA with <CRLF>.<CRLF>
        fwrite($fp, $body . "\r\n.\r\n");
        $this->expect($fp, [250]);

        $this->cmd($fp, "QUIT", [221]);
        fclose($fp);
    }

    private function cmd($fp, string $line, array $okCodes): void
    {
        fwrite($fp, $line . "\r\n");
        $this->expect($fp, $okCodes);
    }

    private function expect($fp, array $okCodes): void
    {
        $data = '';
        while (!feof($fp)) {
            $line = fgets($fp, 515);
            if ($line === false) break;
            $data .= $line;
            // multi-line ends when 4th char is space
            if (strlen($line) >= 4 && $line[3] === ' ') break;
        }
        if ($data === '') throw new \RuntimeException("SMTP no response");
        $code = (int)substr(trim($data), 0, 3);
        if (!in_array($code, $okCodes, true)) {
            throw new \RuntimeException("SMTP unexpected response: " . trim($data));
        }
    }

    private function encodeSubject(string $s): string
    {
        // RFC2047 UTF-8 base64
        return "=?UTF-8?B?" . base64_encode($s) . "?=";
    }

    private function encodeName(string $name): string
    {
        $name = trim($name);
        if ($name === '') return '';
        return "=?UTF-8?B?" . base64_encode($name) . "?=";
    }
}
