<?php

declare(strict_types=1);

final class Mailer
{
    public static function send(array $cfg, string $to, string $subject, string $htmlBody): void
    {
        $to = trim($to);
        if ($to === '') {
            throw new InvalidArgumentException('Recipient email is required');
        }

        if (!empty($cfg['smtp_host']) && !empty($cfg['smtp_user'])) {
            self::sendSmtp($cfg, $to, $subject, $htmlBody);
            return;
        }

        $headers = [];
        $headers[] = 'MIME-Version: 1.0';
        $headers[] = 'Content-type: text/html; charset=utf-8';
        $headers[] = 'From: ' . $cfg['from_name'] . ' <' . $cfg['from_email'] . '>';

        $ok = mail($to, $subject, $htmlBody, implode("\r\n", $headers));
        if (!$ok) {
            throw new RuntimeException('mail() failed');
        }
    }

    private static function sendSmtp(array $cfg, string $to, string $subject, string $htmlBody): void
    {
        $host = trim((string)$cfg['smtp_host']);
        $host = preg_replace('#^\w+://#', '', $host);
        $port = (int)($cfg['smtp_port'] ?? 587);
        $secure = $cfg['smtp_secure'] ?? 'tls';
        // Port 465 typically expects implicit SSL (SMTPS), not STARTTLS.
        if ($port === 465 && $secure === 'tls') {
            $secure = 'ssl';
        }

        $target = $host . ':' . $port;
        if ($secure === 'ssl') {
            $target = 'ssl://' . $target;
        }

        $fp = stream_socket_client($target, $errno, $errstr, 30);
        if (!$fp) {
            // Some Windows environments can fail hostname resolution within stream_socket_client.
            // Try resolving and connecting to the resolved IP while keeping headers/EHLO as the original host.
            $resolved = @gethostbyname($host);
            if (is_string($resolved) && $resolved !== '' && $resolved !== $host) {
                $ipTarget = $resolved . ':' . $port;
                if ($secure === 'ssl') {
                    $ipTarget = 'ssl://' . $ipTarget;
                }
                $fp = stream_socket_client($ipTarget, $errno2, $errstr2, 30);
                if ($fp) {
                    $target = $ipTarget;
                    $errno = $errno2;
                    $errstr = $errstr2;
                }
            }
        }
        if (!$fp) {
            throw new RuntimeException('SMTP connect failed (' . $target . '): ' . $errstr);
        }

        self::expect($fp, 220);
        $from = (string)($cfg['from_email'] ?? '');
        $ehlo = 'localhost';
        if (strpos($from, '@') !== false) {
            $domain = trim(substr($from, (int)strpos($from, '@') + 1));
            if ($domain !== '') {
                $ehlo = $domain;
            }
        }
        self::cmd($fp, 'EHLO ' . $ehlo);
        self::expect($fp, 250);

        if ($secure === 'tls') {
            self::cmd($fp, 'STARTTLS');
            self::expect($fp, 220);
            if (!stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                throw new RuntimeException('STARTTLS failed');
            }
            self::cmd($fp, 'EHLO ' . $ehlo);
            self::expect($fp, 250);
        }

        self::cmd($fp, 'AUTH LOGIN');
        self::expect($fp, 334);
        self::cmd($fp, base64_encode($cfg['smtp_user']));
        self::expect($fp, 334);
        self::cmd($fp, base64_encode($cfg['smtp_pass']));
        self::expect($fp, 235);

        $from = (string)$cfg['from_email'];
        self::cmd($fp, 'MAIL FROM:<' . $from . '>');
        self::expect($fp, 250);
        self::cmd($fp, 'RCPT TO:<' . $to . '>');
        self::expect($fp, 250);
        self::cmd($fp, 'DATA');
        self::expect($fp, 354);

        $textBody = trim(strip_tags(str_replace(['<br>', '<br/>', '<br />', '</p>'], ["\n", "\n", "\n", "\n\n"], $htmlBody)));
        if ($textBody === '') {
            $textBody = 'Please view this email in an HTML-capable client.';
        }
        $boundary = 'b1_' . bin2hex(random_bytes(12));

        $headers = [];
        $headers[] = 'From: ' . $cfg['from_name'] . ' <' . $from . '>';
        $headers[] = 'Reply-To: ' . $cfg['from_name'] . ' <' . $from . '>';
        $headers[] = 'To: <' . $to . '>';
        $headers[] = 'Subject: ' . $subject;
        $headers[] = 'Date: ' . gmdate('D, d M Y H:i:s') . ' +0000';
        $headers[] = 'Message-ID: <' . bin2hex(random_bytes(16)) . '@' . $host . '>';
        $headers[] = 'X-Mailer: SuperbSystemsAcademy';
        $headers[] = 'MIME-Version: 1.0';
        $headers[] = 'Content-Type: multipart/alternative; boundary="' . $boundary . '"';

        $parts = [];
        $parts[] = '--' . $boundary;
        $parts[] = 'Content-Type: text/plain; charset=utf-8';
        $parts[] = 'Content-Transfer-Encoding: quoted-printable';
        $parts[] = '';
        $parts[] = quoted_printable_encode($textBody);
        $parts[] = '';
        $parts[] = '--' . $boundary;
        $parts[] = 'Content-Type: text/html; charset=utf-8';
        $parts[] = 'Content-Transfer-Encoding: quoted-printable';
        $parts[] = '';
        $parts[] = quoted_printable_encode($htmlBody);
        $parts[] = '';
        $parts[] = '--' . $boundary . '--';

        $msg = implode("\r\n", $headers) . "\r\n\r\n" . implode("\r\n", $parts) . "\r\n.";
        fwrite($fp, $msg . "\r\n");
        self::expect($fp, 250);

        self::cmd($fp, 'QUIT');
        fclose($fp);
    }

    private static function cmd($fp, string $cmd): void
    {
        fwrite($fp, $cmd . "\r\n");
    }

    private static function expect($fp, int $code): void
    {
        $data = '';
        while (($line = fgets($fp, 515)) !== false) {
            $data .= $line;
            if (preg_match('/^\d{3} /', $line)) {
                break;
            }
        }
        if (!str_starts_with($data, (string)$code)) {
            throw new RuntimeException('SMTP unexpected: ' . trim($data));
        }
    }
}
