<?php

declare(strict_types=1);

class Mailer
{
    

    public static function send(string $to, string $subject, string $htmlBody, ?string $replyTo = null): bool
    {
         
        $to = self::sanitizeHeaderValue($to);
        $subject = self::sanitizeHeaderValue($subject);
        if ($replyTo !== null) {
            $replyTo = self::sanitizeHeaderValue($replyTo);
        }

        $html = self::wrapTemplate($htmlBody);
        $method = (string) Setting::get('mail_method', 'php');

        switch ($method) {
            case 'smtp':
                return self::sendSmtp($to, $subject, $html, $replyTo);
            case 'resend':
            case 'mailgun':
            case 'brevo':
            case 'postmark':
            case 'sendgrid':
                return self::sendApi($method, $to, $subject, $html, $replyTo);
            default:
                return self::sendPhpMail($to, $subject, $html, $replyTo);
        }
    }

    

    public static function sendType(string $key, string $to, array $vars = [], ?string $replyTo = null, array $htmlVars = []): bool
    {
        if (!isset(MailTypes::all()[$key]) || !MailTypes::isEnabled($key)) {
            return false;
        }
        $b = self::substitute($key, $vars, $htmlVars, null, null);
        return self::send($to, $b['subject'], $b['body'], $replyTo);
    }

     
    private static function substitute(string $key, array $vars, array $htmlVars, ?string $subjectTpl, ?string $bodyTpl): array
    {
        $subjectTpl = $subjectTpl ?? MailTypes::subject($key);
        $bodyTpl    = $bodyTpl ?? MailTypes::body($key);
        $subjVars = [];
        $bodyVars = [];
        foreach ($vars as $k => $v) {
            $subjVars[$k] = self::sanitizeHeaderValue((string) $v);
            $bodyVars[$k] = htmlspecialchars((string) $v);
        }
        foreach ($htmlVars as $k => $v) {
            $bodyVars[$k] = (string) $v;
            $subjVars[$k] = '';
        }
        return ['subject' => strtr($subjectTpl, $subjVars), 'body' => strtr($bodyTpl, $bodyVars)];
    }

    

    public static function previewType(string $key, array $vars, array $htmlVars = [], ?string $subjectTpl = null, ?string $bodyTpl = null): array
    {
        if (!isset(MailTypes::all()[$key])) {
            return ['subject' => '', 'html' => ''];
        }
        $b = self::substitute($key, $vars, $htmlVars, $subjectTpl, $bodyTpl);
        return ['subject' => $b['subject'], 'html' => self::wrapTemplate($b['body'])];
    }

    private static function sanitizeHeaderValue(string $value): string
    {
        return str_replace(["\r", "\n", "\0"], '', $value);
    }

    

    public static function test(string $to): array
    {
        $siteName = Setting::get('site_name', 'TubePress');
        $subject = "[{$siteName}] Test Email";
        $body = '<h2 style="margin:0 0 12px;color:#333;font-size:18px;">Test Email</h2>'
              . '<p style="margin:0 0 16px;color:#555;font-size:15px;line-height:1.6;">This is a test email from <strong>' . htmlspecialchars($siteName) . '</strong>.</p>'
              . '<p style="margin:0;color:#555;font-size:15px;line-height:1.6;">If you received this message, your email configuration is working correctly.</p>';

        try {
            $ok = self::send($to, $subject, $body);
            if ($ok) {
                return ['success' => true, 'error' => null];
            }
            return ['success' => false, 'error' => 'The mail function returned false. Check your server mail configuration.'];
        } catch (\Throwable $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }

     

    private static function sendPhpMail(string $to, string $subject, string $html, ?string $replyTo): bool
    {
        $fromEmail = Setting::get('smtp_from_email', '');
        $fromName  = Setting::get('smtp_from_name', '') ?: Setting::get('site_name', 'TubePress');

        if ($fromEmail === '') {
            $fromEmail = 'noreply@' . ($_SERVER['SERVER_NAME'] ?? 'localhost');
        }

        $headers  = "MIME-Version: 1.0\r\n";
        $headers .= "Content-Type: text/html; charset=UTF-8\r\n";
        $headers .= "From: {$fromName} <{$fromEmail}>\r\n";
        if ($replyTo) {
            $headers .= "Reply-To: {$replyTo}\r\n";
        }
        $headers .= "X-Mailer: TubePress\r\n";

        return @mail($to, $subject, $html, $headers);
    }

     

    private static function sendSmtp(string $to, string $subject, string $html, ?string $replyTo): bool
    {
        $host       = Setting::get('smtp_host', '');
        $port       = (int) Setting::get('smtp_port', '587');
        $encryption = Setting::get('smtp_encryption', 'tls');
        $user       = Setting::get('smtp_user', '');
        $pass       = Setting::get('smtp_pass', '');
        $fromEmail  = Setting::get('smtp_from_email', '');
        $fromName   = Setting::get('smtp_from_name', '') ?: Setting::get('site_name', 'TubePress');

        if ($host === '') {
            throw new \RuntimeException('SMTP host is not configured.');
        }
        if ($fromEmail === '') {
            $fromEmail = $user ?: ('noreply@' . ($_SERVER['SERVER_NAME'] ?? 'localhost'));
        }

         
        $boundary = md5((string) time());
        $msg  = "MIME-Version: 1.0\r\n";
        $msg .= "Date: " . date('r') . "\r\n";
        $msg .= "From: {$fromName} <{$fromEmail}>\r\n";
        $msg .= "To: {$to}\r\n";
        if ($replyTo) {
            $msg .= "Reply-To: {$replyTo}\r\n";
        }
        $msg .= "Subject: {$subject}\r\n";
        $msg .= "Content-Type: text/html; charset=UTF-8\r\n";
        $msg .= "Content-Transfer-Encoding: base64\r\n";
        $msg .= "X-Mailer: TubePress\r\n";
        $msg .= "\r\n";
        $msg .= chunk_split(base64_encode($html));

         
        $addr = ($encryption === 'ssl') ? "ssl://{$host}" : $host;
        $verifyPeer = Setting::get('smtp_verify_peer', '1') === '1';
        $ctx = stream_context_create(['ssl' => [
            'verify_peer' => $verifyPeer,
            'verify_peer_name' => $verifyPeer,
            'allow_self_signed' => !$verifyPeer,
        ]]);

        $socket = @stream_socket_client("{$addr}:{$port}", $errno, $errstr, 15, STREAM_CLIENT_CONNECT, $ctx);
        if (!$socket) {
            throw new \RuntimeException("SMTP connection failed: {$errstr} ({$errno})");
        }

        stream_set_timeout($socket, 15);

         
        $read = function () use ($socket): string {
            $response = '';
            while ($line = fgets($socket, 512)) {
                $response .= $line;
                if (isset($line[3]) && $line[3] === ' ') break;
            }
            return $response;
        };

        $write = function (string $cmd) use ($socket): void {
            fwrite($socket, $cmd . "\r\n");
        };

        $expect = function (int $code, string $context) use ($read): string {
            $resp = $read();
            if ((int) substr($resp, 0, 3) !== $code) {
                throw new \RuntimeException("SMTP error ({$context}): {$resp}");
            }
            return $resp;
        };

        try {
             
            $expect(220, 'connect');

             
            $write("EHLO " . gethostname());
            $expect(250, 'EHLO');

             
            if ($encryption === 'tls') {
                $write("STARTTLS");
                $expect(220, 'STARTTLS');
                $cryptoOk = stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT);
                if (!$cryptoOk) {
                    throw new \RuntimeException('STARTTLS handshake failed.');
                }
                $write("EHLO " . gethostname());
                $expect(250, 'EHLO after STARTTLS');
            }

             
            if ($user !== '' && $pass !== '') {
                $write("AUTH LOGIN");
                $expect(334, 'AUTH LOGIN');
                $write(base64_encode($user));
                $expect(334, 'username');
                $write(base64_encode($pass));
                $expect(235, 'password');
            }

             
            $write("MAIL FROM:<{$fromEmail}>");
            $expect(250, 'MAIL FROM');

            $write("RCPT TO:<{$to}>");
            $expect(250, 'RCPT TO');

             
            $write("DATA");
            $expect(354, 'DATA');
            fwrite($socket, $msg . "\r\n.\r\n");
            $expect(250, 'message accepted');

             
            $write("QUIT");

        } finally {
            @fclose($socket);
        }

        return true;
    }

     

     
    private static function fromParts(): array
    {
        $fromEmail = trim((string) Setting::get('smtp_from_email', ''));
        $fromName  = trim((string) Setting::get('smtp_from_name', '')) ?: (string) Setting::get('site_name', 'TubePress');
        if ($fromEmail === '') {
            $fromEmail = 'noreply@' . ($_SERVER['SERVER_NAME'] ?? 'localhost');
        }
        return [$fromEmail, $fromName];
    }

     
    public static function fromInfo(): array
    {
        return self::fromParts();
    }

    private static function sendApi(string $provider, string $to, string $subject, string $html, ?string $replyTo): bool
    {
        [$fromEmail, $fromName] = self::fromParts();

        switch ($provider) {
            case 'resend':
                $key = trim((string) Setting::get('mail_resend_key', ''));
                if ($key === '') { throw new \RuntimeException('Resend API key is not configured.'); }
                $payload = ['from' => "{$fromName} <{$fromEmail}>", 'to' => [$to], 'subject' => $subject, 'html' => $html];
                if ($replyTo) { $payload['reply_to'] = $replyTo; }
                [$code, $resp] = self::httpJson('https://api.resend.com/emails', $payload, ['Authorization: Bearer ' . $key]);
                return self::apiOk($code, $resp, [200, 201]);

            case 'brevo':
                $key = trim((string) Setting::get('mail_brevo_key', ''));
                if ($key === '') { throw new \RuntimeException('Brevo API key is not configured.'); }
                $payload = ['sender' => ['name' => $fromName, 'email' => $fromEmail], 'to' => [['email' => $to]], 'subject' => $subject, 'htmlContent' => $html];
                if ($replyTo) { $payload['replyTo'] = ['email' => $replyTo]; }
                [$code, $resp] = self::httpJson('https://api.brevo.com/v3/smtp/email', $payload, ['api-key: ' . $key]);
                return self::apiOk($code, $resp, [200, 201, 202]);

            case 'postmark':
                $token = trim((string) Setting::get('mail_postmark_token', ''));
                if ($token === '') { throw new \RuntimeException('Postmark server token is not configured.'); }
                $payload = ['From' => "{$fromName} <{$fromEmail}>", 'To' => $to, 'Subject' => $subject, 'HtmlBody' => $html, 'MessageStream' => 'outbound'];
                if ($replyTo) { $payload['ReplyTo'] = $replyTo; }
                [$code, $resp] = self::httpJson('https://api.postmarkapp.com/email', $payload, ['X-Postmark-Server-Token: ' . $token]);
                return self::apiOk($code, $resp, [200]);

            case 'sendgrid':
                $key = trim((string) Setting::get('mail_sendgrid_key', ''));
                if ($key === '') { throw new \RuntimeException('SendGrid API key is not configured.'); }
                $payload = [
                    'personalizations' => [['to' => [['email' => $to]]]],
                    'from' => ['email' => $fromEmail, 'name' => $fromName],
                    'subject' => $subject,
                    'content' => [['type' => 'text/html', 'value' => $html]],
                ];
                if ($replyTo) { $payload['reply_to'] = ['email' => $replyTo]; }
                [$code, $resp] = self::httpJson('https://api.sendgrid.com/v3/mail/send', $payload, ['Authorization: Bearer ' . $key]);
                return self::apiOk($code, $resp, [200, 202]);

            case 'mailgun':
                $key    = trim((string) Setting::get('mail_mailgun_key', ''));
                $domain = trim((string) Setting::get('mail_mailgun_domain', ''));
                if ($key === '' || $domain === '') { throw new \RuntimeException('Mailgun API key and domain are required.'); }
                $base = (Setting::get('mail_mailgun_region', 'us') === 'eu') ? 'api.eu.mailgun.net' : 'api.mailgun.net';
                $form = ['from' => "{$fromName} <{$fromEmail}>", 'to' => $to, 'subject' => $subject, 'html' => $html];
                if ($replyTo) { $form['h:Reply-To'] = $replyTo; }
                [$code, $resp] = self::httpForm("https://{$base}/v3/{$domain}/messages", $form, 'api:' . $key);
                return self::apiOk($code, $resp, [200]);
        }

        throw new \RuntimeException('Unknown mail provider: ' . $provider);
    }

    private static function apiOk(int $code, string $resp, array $okCodes): bool
    {
        if (in_array($code, $okCodes, true)) {
            return true;
        }
        throw new \RuntimeException("Mail API returned HTTP {$code}: " . mb_substr($resp, 0, 300));
    }

     
    private static function httpJson(string $url, array $payload, array $headers): array
    {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            CURLOPT_HTTPHEADER => array_merge(['Content-Type: application/json', 'Accept: application/json'], $headers),
            CURLOPT_TIMEOUT => 20,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_USERAGENT => 'TubePress',
        ]);
        $resp = curl_exec($ch);
        if ($resp === false) { $err = curl_error($ch); curl_close($ch); throw new \RuntimeException('HTTP error: ' . $err); }
        $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        return [$code, (string) $resp];
    }

     
    private static function httpForm(string $url, array $form, string $basicAuth): array
    {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($form),
            CURLOPT_USERPWD => $basicAuth,
            CURLOPT_TIMEOUT => 20,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_USERAGENT => 'TubePress',
        ]);
        $resp = curl_exec($ch);
        if ($resp === false) { $err = curl_error($ch); curl_close($ch); throw new \RuntimeException('HTTP error: ' . $err); }
        $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        return [$code, (string) $resp];
    }

     

    private static function wrapTemplate(string $body): string
    {
        $siteName = htmlspecialchars(Setting::get('site_name', 'TubePress'));
        $year = date('Y');
        $primaryColor = Setting::get('color_primary', '#6366f1') ?: '#6366f1';
         
        if (!preg_match('/^#[0-9a-fA-F]{3,8}$/', $primaryColor)) {
            $primaryColor = '#6366f1';
        }

        return <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{$siteName}</title>
</head>
<body style="margin:0;padding:0;background-color:#f4f4f7;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f4f4f7;">
<tr><td align="center" style="padding:24px 16px;">

<!-- Card -->
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background-color:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.06);">

<!-- Header -->
<tr>
<td style="padding:28px 32px 20px;border-bottom:3px solid {$primaryColor};">
<h1 style="margin:0;font-size:20px;font-weight:700;color:#1a1a2e;">{$siteName}</h1>
</td>
</tr>

<!-- Body -->
<tr>
<td style="padding:28px 32px 32px;">
{$body}
</td>
</tr>

<!-- Footer -->
<tr>
<td style="padding:20px 32px;background-color:#f9fafb;border-top:1px solid #e5e7eb;">
<p style="margin:0;font-size:12px;color:#9ca3af;line-height:1.5;">
Sent by {$siteName} &middot; {$year}
</p>
</td>
</tr>

</table>
<!-- /Card -->

</td></tr>
</table>
</body>
</html>
HTML;
    }
}
