<?php
/**
 * Smtp — envoi d'e-mails HTML via socket SMTP.
 *
 * Lit la configuration depuis la table settings (smtp_host, smtp_port,
 * smtp_encryption, smtp_auth_enabled, smtp_username, smtp_password,
 * smtp_from_address, smtp_from_name).
 *
 * Supporte : connexion directe, SSL, STARTTLS (certificats auto-signés
 * acceptés), AUTH LOGIN optionnel, pièce jointe logo inline (CID).
 */
class Smtp
{
    /**
     * Envoie un e-mail HTML.
     *
     * @param string|string[] $to        Destinataire(s)
     * @param string          $subject   Sujet
     * @param string          $htmlBody  Corps HTML
     * @param ?string         $logoPath  Chemin absolu d'un logo à embarquer (CID)
     * @return array{ok: bool, msg: string, log: string[]}
     */
    public static function send(
        string|array $to,
        string $subject,
        string $htmlBody,
        ?string $logoPath = null
    ): array {
        $s = Settings::getInstance();

        $host     = $s->get('smtp_host', '');
        $port     = (int)$s->get('smtp_port', 587);
        $enc      = $s->get('smtp_encryption', 'tls');
        $authOn   = $s->get('smtp_auth_enabled', '1') === '1';
        $user     = $s->get('smtp_username', '');
        $pass     = Crypto::decrypt($s->get('smtp_password', '')) ?? '';
        $fromAddr = $s->get('smtp_from_address', '');
        $fromName = $s->get('smtp_from_name', '') ?: $s->get('app_title', 'App');

        if ($host === '') {
            return ['ok' => false, 'msg' => 'SMTP host not configured.', 'log' => []];
        }

        $recipients = is_array($to) ? $to : [$to];
        $recipients = array_filter(array_map('trim', $recipients));
        if (empty($recipients)) {
            return ['ok' => false, 'msg' => 'No recipient.', 'log' => []];
        }
        if ($fromAddr === '') { $fromAddr = $recipients[0]; }

        /* ── MIME ──────────────────────────────────────────────────── */
        $msg = self::buildMime($fromName, $fromAddr, $recipients, $subject, $htmlBody, $logoPath);

        /* ── Socket SMTP ──────────────────────────────────────────── */
        return self::sendViaSocket(
            $host, $port, $enc, $authOn, $user, $pass,
            $fromAddr, $recipients, $msg
        );
    }

    /**
     * Résout le chemin du logo applicatif.
     */
    public static function resolveLogoPath(): ?string
    {
        $rel = Settings::getInstance()->get('app_logo', '');
        if ($rel !== '' && defined('WEBROOT_PATH') && is_file(WEBROOT_PATH . '/' . ltrim($rel, '/'))) {
            return WEBROOT_PATH . '/' . ltrim($rel, '/');
        }
        return null;
    }

    /* ══════════════════════════════════════════════════════════════════
     *  Private helpers
     * ══════════════════════════════════════════════════════════════════ */

    private static function buildMime(
        string $fromName, string $fromAddr, array $recipients,
        string $subject, string $htmlBody, ?string $logoPath
    ): string {
        $boundary = '----=_Part_' . bin2hex(random_bytes(8));
        $cid      = 'logo@' . gethostname();
        $hasLogo  = $logoPath && is_file($logoPath);

        // Injecter le logo CID dans le HTML si présent
        if ($hasLogo && !str_contains($htmlBody, 'cid:')) {
            $htmlBody = '<div style="text-align:center;margin-bottom:16px">'
                      . '<img src="cid:' . $cid . '" alt="' . htmlspecialchars($fromName) . '" style="max-height:60px">'
                      . '</div>' . $htmlBody;
        }

        $msg  = "MIME-Version: 1.0\r\n";
        $msg .= "From: {$fromName} <{$fromAddr}>\r\n";
        $msg .= "To: " . implode(', ', $recipients) . "\r\n";
        $msg .= "Subject: {$subject}\r\n";
        $msg .= "Content-Type: multipart/related; boundary=\"{$boundary}\"\r\n";
        $msg .= "\r\n--{$boundary}\r\n";
        $msg .= "Content-Type: text/html; charset=UTF-8\r\n";
        $msg .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
        $msg .= $htmlBody . "\r\n";

        if ($hasLogo) {
            $ext  = strtolower(pathinfo($logoPath, PATHINFO_EXTENSION));
            $mime = match($ext) {
                'png' => 'image/png', 'svg' => 'image/svg+xml',
                'gif' => 'image/gif', default => 'image/jpeg'
            };
            $b64 = base64_encode(file_get_contents($logoPath));
            $msg .= "--{$boundary}\r\n";
            $msg .= "Content-Type: {$mime}; name=\"logo.{$ext}\"\r\n";
            $msg .= "Content-Transfer-Encoding: base64\r\n";
            $msg .= "Content-ID: <{$cid}>\r\n";
            $msg .= "Content-Disposition: inline\r\n\r\n";
            $msg .= chunk_split($b64, 76, "\r\n");
        }
        $msg .= "--{$boundary}--\r\n";

        return $msg;
    }

    private static function sendViaSocket(
        string $host, int $port, string $enc, bool $authOn,
        string $user, string $pass,
        string $fromAddr, array $recipients, string $msg
    ): array {
        $log = [];
        $ok  = true;
        $err = '';

        try {
            $sslCtx = stream_context_create(['ssl' => [
                'verify_peer'       => false,
                'verify_peer_name'  => false,
                'allow_self_signed' => true,
            ]]);
            $prefix = $enc === 'ssl' ? 'ssl://' : '';
            $errno  = 0; $errstr = '';
            $fp = @stream_socket_client(
                $prefix . $host . ':' . $port, $errno, $errstr, 10,
                STREAM_CLIENT_CONNECT, $sslCtx
            );
            if (!$fp) {
                return ['ok' => false, 'msg' => "Connection failed: {$errstr} ({$errno})", 'log' => $log];
            }
            stream_set_timeout($fp, 10);

            $read = function () use ($fp, &$log): string {
                $resp = '';
                while ($line = fgets($fp, 512)) {
                    $resp .= $line;
                    if (isset($line[3]) && $line[3] === ' ') break;
                }
                $log[] = 'S: ' . trim($resp);
                return $resp;
            };
            $send = function (string $cmd) use ($fp, &$log): void {
                $log[] = 'C: ' . trim($cmd);
                fwrite($fp, $cmd . "\r\n");
            };
            $check = fn(string $resp, string $code): bool => str_starts_with($resp, $code);

            $resp = $read();
            if (!$check($resp, '220')) { $ok = false; $err = $resp; }

            if ($ok) {
                $send('EHLO ' . gethostname());
                $resp = $read();
                if (!$check($resp, '250')) { $ok = false; $err = $resp; }
            }

            if ($ok && $enc === 'tls') {
                $send('STARTTLS');
                $resp = $read();
                if ($check($resp, '220')) {
                    $crypto = @stream_socket_enable_crypto($fp, true,
                        STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT);
                    if ($crypto !== true) {
                        $ok = false; $err = 'STARTTLS handshake failed';
                        $log[] = 'PHP: ' . $err;
                    } else {
                        $send('EHLO ' . gethostname());
                        $read();
                    }
                } else { $ok = false; $err = 'STARTTLS rejected: ' . trim($resp); }
            }

            if ($ok && $authOn && $user !== '') {
                $send('AUTH LOGIN');
                $resp = $read();
                if ($check($resp, '334')) {
                    $send(base64_encode($user));
                    $read();
                    $send(base64_encode($pass));
                    $resp = $read();
                    if (!$check($resp, '235')) { $ok = false; $err = 'AUTH failed: ' . trim($resp); }
                }
            }

            if ($ok) {
                $send("MAIL FROM:<{$fromAddr}>");
                $resp = $read();
                if (!$check($resp, '250')) { $ok = false; $err = $resp; }
            }

            if ($ok) {
                foreach ($recipients as $rcpt) {
                    $send("RCPT TO:<{$rcpt}>");
                    $resp = $read();
                    if (!$check($resp, '250')) { $ok = false; $err = $resp; break; }
                }
            }

            if ($ok) {
                $send('DATA');
                $resp = $read();
                if ($check($resp, '354')) {
                    fwrite($fp, $msg . "\r\n.\r\n");
                    $resp = $read();
                    if (!$check($resp, '250')) { $ok = false; $err = $resp; }
                } else { $ok = false; $err = $resp; }
            }

            @fwrite($fp, "QUIT\r\n");
            $log[] = 'C: QUIT';
            @fclose($fp);

        } catch (\Throwable $e) {
            $ok  = false;
            $err = $e->getMessage();
            $log[] = 'PHP: ' . $err;
            if (isset($fp) && is_resource($fp)) @fclose($fp);
        }

        return ['ok' => $ok, 'msg' => $ok ? 'Sent.' : trim($err), 'log' => $log];
    }
}
