<?php

namespace App\Services;

/**
 * ITSWEBER CMS – Mailer
 *
 * Lightweight mail service with two transport options:
 *   'mail' – PHP's built-in mail() function (default, works on most shared hosting)
 *   'smtp' – Native SMTP client via stream_socket_client (no dependencies)
 *
 * Configuration (via app/Config/app.php, key 'mail'):
 *   transport       – 'mail' | 'smtp'  (default: 'mail')
 *   from_address    – sender e-mail address (required)
 *   from_name       – sender display name (optional)
 *   reply_to        – reply-to address (optional, defaults to from_address)
 *   smtp_host       – SMTP hostname (required when transport = 'smtp')
 *   smtp_port       – SMTP port, e.g. 587 (TLS), 465 (SSL), 25 (plain)
 *   smtp_username   – SMTP login username
 *   smtp_password   – SMTP login password
 *   smtp_encryption – 'tls' (STARTTLS), 'ssl' (implicit TLS), 'none'
 *
 * Usage:
 *   $mailer = new Mailer($config['mail'] ?? []);
 *   $ok = $mailer->send('user@example.com', 'Subject', $textBody);
 *   $ok = $mailer->sendHtml('user@example.com', 'Subject', $htmlBody);
 *
 * Callers do not need to know which transport is active.
 * To switch from mail() to SMTP, only the config needs to change.
 */
class Mailer
{
    private string $fromAddress;
    private string $fromName;
    private string $replyTo;

    // Transport
    private string $transport;        // 'mail' | 'smtp'

    // SMTP options (only used when transport = 'smtp')
    private string $smtpHost;
    private int    $smtpPort;
    private string $smtpUsername;
    private string $smtpPassword;
    private string $smtpEncryption;   // 'none' | 'tls' | 'ssl'

    public function __construct(array $config = [])
    {
        $this->fromAddress    = $config['from_address']    ?? 'noreply@localhost';
        $this->fromName       = $config['from_name']       ?? 'ITSWEBER CMS';
        $this->replyTo        = $config['reply_to']        ?? '';
        $this->transport      = $config['transport']       ?? 'mail';
        $this->smtpHost       = $config['smtp_host']       ?? '';
        $this->smtpPort       = (int) ($config['smtp_port'] ?? 587);
        $this->smtpUsername   = $config['smtp_username']   ?? '';
        $this->smtpPassword   = $config['smtp_password']   ?? '';
        $this->smtpEncryption = $config['smtp_encryption'] ?? 'tls';

        if ($this->replyTo === '') {
            $this->replyTo = $this->fromAddress;
        }
    }

    // ─────────────────────────────────────────────────────────
    // Public API
    // ─────────────────────────────────────────────────────────

    /**
     * Send a plain-text e-mail.
     *
     * @param string $to      Recipient e-mail address.
     * @param string $subject E-mail subject.
     * @param string $body    Plain-text body.
     * @return bool           True on success, false on failure.
     */
    public function send(string $to, string $subject, string $body): bool
    {
        if (empty($to) || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
            return false;
        }

        $from    = $this->formatAddress($this->fromAddress, $this->fromName);
        $headers = [
            'From: '     . $from,
            'Reply-To: ' . $this->replyTo,
            'MIME-Version: 1.0',
            'Content-Type: text/plain; charset=UTF-8',
            'Content-Transfer-Encoding: 8bit',
            'X-Mailer: ITSWEBER-CMS',
        ];

        if ($this->transport === 'smtp') {
            return $this->sendViaSMTP($to, $subject, $body, $headers);
        }

        return mail($to, $this->encodeSubject($subject), $body, implode("\r\n", $headers));
    }

    /**
     * Send an HTML e-mail with a plain-text fallback.
     *
     * @param string $to        Recipient e-mail address.
     * @param string $subject   E-mail subject.
     * @param string $htmlBody  HTML body.
     * @param string $plainBody Plain-text alternative (auto-derived from HTML if empty).
     * @return bool
     */
    public function sendHtml(string $to, string $subject, string $htmlBody, string $plainBody = ''): bool
    {
        if (empty($to) || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
            return false;
        }

        if ($plainBody === '') {
            $plainBody = strip_tags($htmlBody);
        }

        $boundary = '=_Part_' . md5(uniqid('', true));
        $from     = $this->formatAddress($this->fromAddress, $this->fromName);

        $headers = [
            'From: '     . $from,
            'Reply-To: ' . $this->replyTo,
            'MIME-Version: 1.0',
            'Content-Type: multipart/alternative; boundary="' . $boundary . '"',
            'X-Mailer: ITSWEBER-CMS',
        ];

        $body  = "--{$boundary}\r\n";
        $body .= "Content-Type: text/plain; charset=UTF-8\r\n";
        $body .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
        $body .= $plainBody . "\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 .= $htmlBody . "\r\n\r\n";
        $body .= "--{$boundary}--";

        if ($this->transport === 'smtp') {
            return $this->sendViaSMTP($to, $subject, $body, $headers);
        }

        return mail($to, $this->encodeSubject($subject), $body, implode("\r\n", $headers));
    }

    // ─────────────────────────────────────────────────────────
    // SMTP transport
    // ─────────────────────────────────────────────────────────

    /**
     * Send a message via a direct SMTP connection.
     *
     * Supports:
     *   - Plain connection (port 25, encryption=none)
     *   - Implicit TLS / SMTPS (port 465, encryption=ssl)
     *   - STARTTLS (port 587, encryption=tls)
     *   - AUTH LOGIN with username/password
     *
     * @param string   $to           Recipient e-mail.
     * @param string   $subject      Un-encoded subject line.
     * @param string   $body         Message body (plain or MIME multipart).
     * @param string[] $extraHeaders Additional RFC 2822 headers (From, Reply-To, etc.).
     * @return bool
     */
    private function sendViaSMTP(
        string $to,
        string $subject,
        string $body,
        array  $extraHeaders
    ): bool {
        $host       = $this->smtpHost;
        $port       = $this->smtpPort;
        $encryption = strtolower($this->smtpEncryption);
        $timeout    = 15;

        if (empty($host)) {
            error_log('ITSWEBER Mailer: SMTP transport requested but smtp_host is empty.');
            return false;
        }

        // Build the socket URI
        $socketUri = ($encryption === 'ssl')
            ? "ssl://{$host}:{$port}"
            : "tcp://{$host}:{$port}";

        $socket = @stream_socket_client($socketUri, $errno, $errstr, $timeout);

        if ($socket === false) {
            error_log("ITSWEBER Mailer SMTP: cannot connect to {$socketUri} – {$errstr} (errno {$errno})");
            return false;
        }

        // Give the socket a read/write timeout
        stream_set_timeout($socket, $timeout);

        try {
            // ── Greeting ──────────────────────────────────────────────────
            $line = $this->smtpRead($socket);
            if (!$this->smtpOk($line, 220)) {
                $this->smtpQuit($socket);
                return false;
            }

            $ehlo = 'ITSWEBER-CMS';

            // ── EHLO ──────────────────────────────────────────────────────
            $this->smtpWrite($socket, "EHLO {$ehlo}\r\n");
            $resp = $this->smtpRead($socket);
            if (!$this->smtpOk($resp, 250)) {
                // Fall back to HELO if EHLO not supported
                $this->smtpWrite($socket, "HELO {$ehlo}\r\n");
                $resp = $this->smtpRead($socket);
                if (!$this->smtpOk($resp, 250)) {
                    $this->smtpQuit($socket);
                    return false;
                }
            }

            // ── STARTTLS ──────────────────────────────────────────────────
            if ($encryption === 'tls') {
                $this->smtpWrite($socket, "STARTTLS\r\n");
                $resp = $this->smtpRead($socket);
                if (!$this->smtpOk($resp, 220)) {
                    error_log('ITSWEBER Mailer SMTP: STARTTLS rejected by server.');
                    fclose($socket);
                    return false;
                }

                if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                    error_log('ITSWEBER Mailer SMTP: TLS negotiation failed.');
                    fclose($socket);
                    return false;
                }

                // EHLO again after TLS handshake
                $this->smtpWrite($socket, "EHLO {$ehlo}\r\n");
                $resp = $this->smtpRead($socket);
                if (!$this->smtpOk($resp, 250)) {
                    $this->smtpQuit($socket);
                    return false;
                }
            }

            // ── AUTH LOGIN ────────────────────────────────────────────────
            if ($this->smtpUsername !== '' || $this->smtpPassword !== '') {
                $this->smtpWrite($socket, "AUTH LOGIN\r\n");
                $resp = $this->smtpRead($socket);
                if (!$this->smtpOk($resp, 334)) {
                    error_log('ITSWEBER Mailer SMTP: AUTH LOGIN not accepted.');
                    $this->smtpQuit($socket);
                    return false;
                }

                $this->smtpWrite($socket, base64_encode($this->smtpUsername) . "\r\n");
                $resp = $this->smtpRead($socket);
                if (!$this->smtpOk($resp, 334)) {
                    error_log('ITSWEBER Mailer SMTP: SMTP username rejected.');
                    $this->smtpQuit($socket);
                    return false;
                }

                $this->smtpWrite($socket, base64_encode($this->smtpPassword) . "\r\n");
                $resp = $this->smtpRead($socket);
                if (!$this->smtpOk($resp, 235)) {
                    error_log('ITSWEBER Mailer SMTP: SMTP authentication failed (wrong password?).');
                    $this->smtpQuit($socket);
                    return false;
                }
            }

            // ── MAIL FROM ─────────────────────────────────────────────────
            $this->smtpWrite($socket, "MAIL FROM:<{$this->fromAddress}>\r\n");
            $resp = $this->smtpRead($socket);
            if (!$this->smtpOk($resp, 250)) {
                $this->smtpQuit($socket);
                return false;
            }

            // ── RCPT TO ───────────────────────────────────────────────────
            $this->smtpWrite($socket, "RCPT TO:<{$to}>\r\n");
            $resp = $this->smtpRead($socket);
            if (!$this->smtpOk($resp, 250)) {
                $this->smtpQuit($socket);
                return false;
            }

            // ── DATA ──────────────────────────────────────────────────────
            $this->smtpWrite($socket, "DATA\r\n");
            $resp = $this->smtpRead($socket);
            if (!$this->smtpOk($resp, 354)) {
                $this->smtpQuit($socket);
                return false;
            }

            // Build the full RFC 2822 message
            $message  = 'To: ' . $to . "\r\n";
            $message .= 'Subject: ' . $this->encodeSubject($subject) . "\r\n";
            $message .= 'Date: ' . date('r') . "\r\n";
            $message .= 'Message-ID: <' . uniqid('iw', true) . '@' . ($this->smtpHost) . ">\r\n";
            $message .= implode("\r\n", $extraHeaders) . "\r\n";
            $message .= "\r\n";
            $message .= $body;

            // RFC 5321 dot-stuffing: lines starting with '.' get an extra dot
            $message = preg_replace('/^(\\.)/m', '..$1', $message);

            $this->smtpWrite($socket, $message . "\r\n.\r\n");
            $resp = $this->smtpRead($socket);
            if (!$this->smtpOk($resp, 250)) {
                $this->smtpQuit($socket);
                return false;
            }

            // ── QUIT ──────────────────────────────────────────────────────
            $this->smtpQuit($socket);
            return true;

        } catch (\Throwable $e) {
            error_log('ITSWEBER Mailer SMTP exception: ' . $e->getMessage());
            if (is_resource($socket)) {
                fclose($socket);
            }
            return false;
        }
    }

    /** Write a line to the SMTP socket. */
    private function smtpWrite($socket, string $data): void
    {
        fwrite($socket, $data);
    }

    /**
     * Read a (potentially multi-line) SMTP response.
     * Lines with a dash in position 3 (e.g. "250-...") indicate continuation.
     */
    private function smtpRead($socket): string
    {
        $response = '';
        while (($line = fgets($socket, 512)) !== false) {
            $response .= $line;
            // If the 4th character is a space (not '-'), this is the last response line
            if (isset($line[3]) && $line[3] === ' ') {
                break;
            }
        }
        return $response;
    }

    /** Check whether an SMTP response starts with the expected status code. */
    private function smtpOk(string $response, int $expectedCode): bool
    {
        return str_starts_with(ltrim($response), (string) $expectedCode);
    }

    /** Send QUIT and close the socket. */
    private function smtpQuit($socket): void
    {
        $this->smtpWrite($socket, "QUIT\r\n");
        fclose($socket);
    }

    // ─────────────────────────────────────────────────────────
    // Encoding helpers
    // ─────────────────────────────────────────────────────────

    private function formatAddress(string $email, string $name): string
    {
        if ($name === '') {
            return $email;
        }
        return $this->encodeName($name) . ' <' . $email . '>';
    }

    private function encodeSubject(string $subject): string
    {
        // RFC 2047 encoded-word for UTF-8 subjects
        return '=?UTF-8?B?' . base64_encode($subject) . '?=';
    }

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