<?php
/**
 * Slate — Mailer.
 *
 * Minimal email sender. Uses PHPMailer if available (vendor/),
 * falls back to PHP's mail() if not. SMTP credentials are read
 * from the `settings` table:
 *
 *   smtp_host, smtp_port, smtp_user, smtp_pass,
 *   smtp_from_email, smtp_from_name, smtp_encryption
 *
 * Plugins that need queueing, retry, logging, or templates wrap
 * this with their own infrastructure.
 */

class Mailer {

    /**
     * Send an email. Returns true on success, false on failure.
     *
     * $toName is optional — most callers don't have the recipient's display
     * name handy, and SMTP doesn't require one. Pass it when you have it
     * (e.g., when sending to a known customer record). The old signature
     * had $toName as the SECOND required argument and exactly one caller
     * (settings.php's test-email button) was calling it with three args
     * `(to, subject, body)`, which PHP mapped positionally as
     * `(to, toName, subject)` and then fatal'd on the missing $bodyHtml
     * — the source of the test-email 500.
     *
     * If $log is true (default), the attempt is recorded to the audit log.
     */
    public static function send(string $to, string $subject, string $bodyHtml, string $toName = '', bool $log = true): bool {
        $host = Database::setting('smtp_host');

        $ok = false;
        $err = '';

        if (class_exists('PHPMailer\\PHPMailer\\PHPMailer') && $host) {
            [$ok, $err] = self::sendViaPHPMailer($to, $toName, $subject, $bodyHtml);
        } else {
            [$ok, $err] = self::sendViaMail($to, $toName, $subject, $bodyHtml);
        }

        if ($log && class_exists('AuditLog')) {
            AuditLog::record('mail.send', $to, [
                'subject' => $subject,
                'ok'      => $ok,
                'error'   => $err,
            ]);
        }

        return $ok;
    }

    /** @return array{0:bool, 1:string} [success, errorMessage] */
    private static function sendViaPHPMailer(string $to, string $toName, string $subject, string $body): array {
        try {
            $mail = new \PHPMailer\PHPMailer\PHPMailer(true);
            $mail->isSMTP();
            $mail->Host = Database::setting('smtp_host');
            $mail->Port = (int)(Database::setting('smtp_port') ?: 587);

            $user = Database::setting('smtp_user');
            $pass = Database::setting('smtp_pass');
            if ($user) {
                $mail->SMTPAuth = true;
                $mail->Username = $user;
                $mail->Password = function_exists('slate_decrypt_secret')
                    ? (slate_decrypt_secret((string)$pass) ?? $pass)
                    : $pass;
            }

            $enc = Database::setting('smtp_encryption') ?: 'tls';
            if ($enc === 'ssl') {
                $mail->SMTPSecure = \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS;
            } elseif ($enc === 'tls' || $enc === 'starttls') {
                $mail->SMTPSecure = \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
            }

            $fromEmail = Database::setting('smtp_from_email') ?: 'no-reply@example.com';
            $fromName  = Database::setting('smtp_from_name')  ?: 'Slate';
            $mail->setFrom($fromEmail, $fromName);
            // Pass the recipient name only when we have one; PHPMailer
            // accepts an empty string but it produces an awkward
            // "<>name@host" header on some MTAs.
            if ($toName !== '') {
                $mail->addAddress($to, $toName);
            } else {
                $mail->addAddress($to);
            }
            $mail->isHTML(true);
            $mail->Subject = $subject;
            $mail->Body    = $body;
            $mail->AltBody = strip_tags($body);

            $mail->send();
            return [true, ''];
        } catch (\Throwable $e) {
            return [false, $e->getMessage()];
        }
    }

    /** @return array{0:bool, 1:string} */
    private static function sendViaMail(string $to, string $toName, string $subject, string $body): array {
        $clean = fn(string $s) => str_replace(["\r", "\n", "\0"], '', $s);
        $to       = $clean($to);
        $toName   = $clean($toName);
        $subject  = $clean($subject);
        $fromMail = $clean((string)(Database::setting('smtp_from_email') ?: 'no-reply@example.com'));
        $fromName = $clean((string)(Database::setting('smtp_from_name')  ?: 'Slate'));

        $headers  = "MIME-Version: 1.0\r\n";
        $headers .= "Content-Type: text/html; charset=UTF-8\r\n";
        $headers .= "From: {$fromName} <{$fromMail}>\r\n";
        $headers .= "Reply-To: {$fromMail}\r\n";

        $ok = @mail($to, $subject, $body, $headers);
        return [(bool)$ok, $ok ? '' : 'mail() returned false'];
    }
}
