<?php

declare(strict_types=1);

namespace PpPhp\Core;

/**
 * Sends PPphp's own account emails from the templates configured in
 * Settings → Email Templates.
 *
 * Each template stores only the *content* of the message. Mailer wraps it in a
 * branded, responsive HTML shell so every email looks consistent and polished
 * without the author having to hand-write layout markup.
 *
 * Email clients are not browsers: the shell uses tables and inline styles only
 * (no CSS variables, no <style> blocks, no flexbox), and avoids gradients in
 * favour of a solid header colour that renders everywhere including Outlook.
 *
 * Every send is best-effort: if email isn't configured yet, or the SMTP server
 * is unreachable, the caller's action still succeeds — nobody should be unable
 * to change their password because a mail server is down.
 */
class Mailer
{
    /** Brand colour for the email header/buttons (email clients can't read CSS vars). */
    private const BRAND = '#4f46e5';

    /**
     * Render a template into a finished email.
     *
     * @return array{subject:string,html:string,text:string}
     */
    public static function render(string $key, array $vars = []): array
    {
        $templates = PlatformSettings::templates();
        $tpl = $templates[$key] ?? null;
        if ($tpl === null) {
            return ['subject' => '', 'html' => '', 'text' => ''];
        }

        $settings = PlatformSettings::get(PlatformSettings::EMAIL);
        $vars += [
            'app_name'   => (string) ($settings['appName'] ?? 'PPphp'),
            'login_url'  => self::url('/login'),
            'review_url' => self::url('/users'),
            'expires_in' => '30 minutes',
        ];

        $subject = PlatformSettings::render((string) $tpl['subject'], $vars);
        $body    = PlatformSettings::render((string) $tpl['body'], $vars);

        // Owner alerts go to the person running the platform and copy in someone
        // who has no account, so the account-holder footer would be wrong for both.
        $note = ((string) ($tpl['to'] ?? 'user')) === 'owner'
            ? "You're receiving this because you are listed as the owner of {app_name}."
            : "You're receiving this because you have an account on {app_name}.";
        $html = self::shell($body, (string) $vars['app_name'], PlatformSettings::render($note, $vars));

        return ['subject' => $subject, 'html' => $html, 'text' => self::toText($body)];
    }

    /**
     * Render and send a template. Never throws — returns false when email is not
     * configured or the send fails, so callers can carry on regardless.
     *
     * @param string $cc Optional address to copy in (e.g. the person who made a request).
     */
    public static function send(string $key, string $to, array $vars = [], string $cc = ''): bool
    {
        if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
            return false;
        }
        if (!PlatformSettings::templateEnabled($key)) {
            return false; // this template is switched off — don't send it
        }
        // primeMailer() now falls back to the email config BAKED into the bundle,
        // so a fresh install sends without any manual setup; a customer's own
        // Settings → Email overrides it the moment they configure it.
        if (!PlatformSettings::primeMailer()) {
            return false;
        }

        $mail = self::render($key, $vars);
        if ($mail['subject'] === '' && $mail['html'] === '') {
            return false;
        }

        $params = [
            'to'       => $to,
            'subject'  => $mail['subject'],
            'htmlbody' => $mail['html'],
            'body'     => $mail['text'],
        ];
        if ($cc !== '' && filter_var($cc, FILTER_VALIDATE_EMAIL) && strcasecmp($cc, $to) !== 0) {
            $params['cc'] = $cc;
            // Replying to the owner's copy should reach the requester directly.
            $params['replyTo'] = $cc;
        }

        try {
            $result = Email::send($params);
            if (!empty($result['mailed'])) {
                return true;
            }
            // Every server failed right now — persist for automatic retry with backoff
            // so a transient all-providers outage doesn't lose the message.
            self::queueForRetry($params, $key, (string) ($result['message'] ?? ''));
            return false;
        } catch (\Throwable $e) {
            error_log('Mailer: ' . $key . ' to ' . $to . ' failed: ' . $e->getMessage());
            self::queueForRetry($params, $key, $e->getMessage());
            return false;
        }
    }

    /** Persist a failed send into the retry queue (best-effort; never throws). */
    private static function queueForRetry(array $params, string $key, string $error): void
    {
        try {
            if (class_exists(MailQueue::class)) {
                MailQueue::enqueue($params, ['template' => $key, 'error' => $error]);
            }
        } catch (\Throwable) {
            // A queue write hiccup must never turn a soft send-failure into a fatal.
        }
    }

    /**
     * Send an owner-bound template to the configured owner address, optionally
     * copying someone in. Returns false when no owner address is configured.
     */
    public static function sendToOwner(string $key, array $vars = [], string $cc = ''): bool
    {
        $owner = PlatformSettings::ownerEmail();
        if ($owner === '') {
            return false;
        }
        return self::send($key, $owner, $vars, $cc);
    }

    /**
     * Wrap template content in the branded HTML shell.
     * Table-based + inline styles so it survives real email clients.
     */
    public static function shell(string $content, string $appName = 'PPphp', string $footerNote = ''): string
    {
        $app  = htmlspecialchars($appName, ENT_QUOTES);
        $year = date('Y');
        $note = htmlspecialchars(
            $footerNote !== '' ? $footerNote : "You're receiving this because you have an account on {$appName}.",
            ENT_QUOTES
        );
        $font = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif";

        return <<<HTML
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{$app}</title>
</head>
<body style="margin:0;padding:0;background:#f4f6fb;-webkit-font-smoothing:antialiased;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f4f6fb;padding:28px 12px;">
  <tr>
    <td align="center">
      <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:560px;background:#ffffff;border:1px solid #e6e9f0;border-radius:14px;overflow:hidden;font-family:{$font};">
        <tr>
          <td style="background:#4f46e5;padding:20px 28px;">
            <span style="color:#ffffff;font-size:17px;font-weight:700;letter-spacing:-0.2px;">{$app}</span>
          </td>
        </tr>
        <tr>
          <td style="padding:28px;color:#1e293b;font-size:14px;line-height:1.65;">
{$content}
          </td>
        </tr>
        <tr>
          <td style="padding:16px 28px;background:#f8fafc;border-top:1px solid #eef1f6;color:#7c8798;font-size:11.5px;line-height:1.55;">
            {$note}<br>
            &copy; {$year} {$app}
          </td>
        </tr>
      </table>
    </td>
  </tr>
</table>
</body>
</html>
HTML;
    }

    /** A ready-made call-to-action button for templates. */
    public static function button(string $url, string $label): string
    {
        $u = htmlspecialchars($url, ENT_QUOTES);
        $l = htmlspecialchars($label, ENT_QUOTES);
        return '<a href="' . $u . '" style="display:inline-block;background:' . self::BRAND
            . ';color:#ffffff;text-decoration:none;font-weight:600;font-size:14px;'
            . 'padding:11px 22px;border-radius:8px;">' . $l . '</a>';
    }

    /** Plain-text fallback for clients that refuse HTML. */
    public static function toText(string $html): string
    {
        $text = preg_replace('/<br\s*\/?>/i', "\n", $html) ?? $html;
        $text = preg_replace('/<\/(p|div|tr|h[1-6])>/i', "\n\n", $text) ?? $text;
        $text = strip_tags($text);
        $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $text = preg_replace("/[ \t]+/", ' ', $text) ?? $text;
        $text = preg_replace("/\n{3,}/", "\n\n", $text) ?? $text;
        return trim($text);
    }

    /** Absolute URL for links inside emails. */
    public static function url(string $path): string
    {
        $base = defined('BASE_URL') ? rtrim((string) BASE_URL, '/') : '';
        $host = (string) ($_SERVER['HTTP_HOST'] ?? 'localhost');
        $scheme = (($_SERVER['HTTPS'] ?? '') === 'on' || ($_SERVER['SERVER_PORT'] ?? '') === '443') ? 'https' : 'http';
        return $scheme . '://' . $host . $base . $path;
    }
}
