<?php
namespace App\Core;

use PDO;

class Mailer
{
    /**
     * Envoi simple d'email via mail() avec en-têtes minimaux.
     * SMTP avancé non implémenté ici; configurez sendmail/SMTP au niveau PHP si nécessaire.
     */
    public static function send(string $toEmail, string $toName, string $subject, string $htmlBody, ?PDO $pdo = null): bool
    {
        $result = self::sendWithReport($toEmail, $toName, $subject, $htmlBody, $pdo);
        return $result['ok'];
    }

    /**
     * Retourne le détail du transport utilisé pour éviter les faux positifs côté UI.
     * Options supportées:
     * - require_smtp: échoue si aucune config SMTP exploitable n'est présente
     * - allow_mail_fallback: autorise le fallback mail() en cas d'échec SMTP
     */
    public static function sendWithReport(string $toEmail, string $toName, string $subject, string $htmlBody, ?PDO $pdo = null, array $options = []): array
    {
        $fromEmail = 'noreply@localhost';
        $fromName  = 'NOVALINK';
        $smtp = [
            'host' => null,
            'port' => 0,
            'encryption' => null, // tls|ssl|null
            'user' => null,
            'pass' => null,
        ];

        // Si settings en DB disponibles, utiliser comme From
        try {
            if ($pdo) {
                $st = $pdo->query("SELECT `key`,`value` FROM settings WHERE `key` IN ('smtp_host','smtp_port','smtp_encryption','smtp_user','smtp_pass','smtp_from_name','app_name')");
                $kv = [];
                foreach ($st->fetchAll() as $r) { $kv[$r['key']] = $r['value']; }
                if (!empty($kv['smtp_user'])) { $fromEmail = $kv['smtp_user']; }
                if (!empty($kv['smtp_from_name'])) {
                    $fromName = $kv['smtp_from_name'];
                } elseif (!empty($kv['app_name'])) {
                    $fromName = $kv['app_name'];
                }
                // Charger config SMTP si disponible
                $smtp['host'] = $kv['smtp_host'] ?? null;
                $smtp['port'] = (int)($kv['smtp_port'] ?? 0);
                $smtp['encryption'] = $kv['smtp_encryption'] ?? null;
                $smtp['user'] = $kv['smtp_user'] ?? null;
                $smtp['pass'] = $kv['smtp_pass'] ?? null;
            }
        } catch (\Throwable $e) { /* ignore */ }

        $requireSmtp = !empty($options['require_smtp']);
        $allowMailFallback = array_key_exists('allow_mail_fallback', $options) ? (bool)$options['allow_mail_fallback'] : true;

        $message = self::buildMimeMessage($toEmail, $toName, $fromEmail, $fromName, $subject, $htmlBody);

        // Préparer message
        $headers   = [];
        $headers[] = 'Date: ' . date(DATE_RFC2822);
        $headers[] = 'MIME-Version: 1.0';
        $headers[] = 'From: ' . self::encodeHeader($fromName) . " <{$fromEmail}>";
        $headers[] = 'Reply-To: ' . $fromEmail;
        $headers[] = 'Message-ID: <' . self::buildMessageId($fromEmail) . '>';
        $headers[] = 'X-Mailer: PHP/' . phpversion();
        $headers[] = 'Content-Type: multipart/alternative; boundary="' . $message['boundary'] . '"';
        $toHeader = self::formatMailbox($toEmail, $toName);

        // Si un hôte SMTP est configuré, tenter l'envoi SMTP direct
        if (!empty($smtp['host']) && $smtp['port'] > 0) {
            $smtpResult = self::sendViaSmtp($smtp, $fromEmail, $toEmail, $toHeader, $subject, $headers, $message['body']);
            if ($smtpResult['ok']) {
                return [
                    'ok' => true,
                    'transport' => 'smtp',
                    'message' => 'Email de test remis au serveur SMTP.',
                ];
            }

            if (!$allowMailFallback) {
                return [
                    'ok' => false,
                    'transport' => 'smtp',
                    'message' => $smtpResult['message'] ?: 'Le serveur SMTP a refusé l\'envoi.',
                ];
            }
        }

        if ($requireSmtp) {
            return [
                'ok' => false,
                'transport' => 'smtp',
                'message' => 'Configuration SMTP incomplète: renseignez au minimum l\'hôte et le port.',
            ];
        }

        $mailSent = @mail($toHeader, self::encodeHeader($subject), $message['body'], implode("\r\n", $headers));

        return [
            'ok' => $mailSent,
            'transport' => 'mail',
            'message' => $mailSent
                ? 'Email remis au transport local mail(). La remise finale dépend du serveur configuré dans PHP.'
                : 'Echec de l\'appel au transport local mail().',
        ];
    }

    private static function encodeHeader(string $str): string
    {
        // Encodage simple pour UTF-8
        return '=?UTF-8?B?'.base64_encode($str).'?=';
    }

    /**
     * Envoi SMTP minimaliste (AUTH LOGIN) avec STARTTLS optionnel.
     */
    private static function sendViaSmtp(array $smtp, string $fromEmail, string $toEmail, string $toHeader, string $subject, array $headers, string $body): array
    {
        $host = $smtp['host'];
        $port = (int)$smtp['port'];
        $enc  = strtolower((string)($smtp['encryption'] ?? ''));
        $user = $smtp['user'] ?? '';
        $pass = $smtp['pass'] ?? '';

        $remote = ($enc === 'ssl') ? "ssl://{$host}:{$port}" : "tcp://{$host}:{$port}";
        $context = stream_context_create([
            'ssl' => [
                'verify_peer' => false, // dev: désactiver validation cert
                'verify_peer_name' => false,
                'allow_self_signed' => true,
            ]
        ]);
        $fp = @stream_socket_client($remote, $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $context);
        if (!$fp) { return ['ok' => false, 'message' => trim("Connexion SMTP impossible: {$errstr} ({$errno})")]; }
        stream_set_timeout($fp, 10);

        $fail = static function(string $message) use ($fp): array {
            if (is_resource($fp)) {
                fclose($fp);
            }
            return ['ok' => false, 'message' => $message];
        };

        $read = static function() use ($fp) {
            $response = '';
            while (($line = fgets($fp, 512)) !== false) {
                $response .= $line;
                if (preg_match('~^\d{3} ~', $line)) {
                    break;
                }
            }
            return $response === '' ? false : $response;
        };
        $send = function(string $cmd) use ($fp) { fwrite($fp, $cmd . "\r\n"); };

        $banner = $read(); if ($banner === false || (int)substr($banner,0,3) >= 400) { return $fail('Bannière SMTP invalide ou absente.'); }
        $send('EHLO localhost');
        $ehlo = $read() ?: '';

        // STARTTLS si tls demandé
        if ($enc === 'tls' && stripos($ehlo, 'STARTTLS') !== false) {
            $send('STARTTLS');
            $resp = $read();
            if ($resp === false || (int)substr($resp,0,3) >= 400) { return $fail(self::formatSmtpError('Le serveur SMTP a refusé STARTTLS.', $resp)); }
            if (!stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { return $fail('Impossible d\'établir le chiffrement TLS avec le serveur SMTP.'); }
            // Re-EHLO après STARTTLS
            $send('EHLO localhost');
            $read();
        } elseif ($enc === 'tls') {
            return $fail('Le serveur SMTP ne propose pas STARTTLS alors que le chiffrement tls est demandé.');
        }

        // AUTH si credentials fournis
        if ($user !== '') {
            $send('AUTH LOGIN');
            $resp = $read(); if ($resp === false || (int)substr($resp,0,3) >= 400) { return $fail(self::formatSmtpError('Le serveur SMTP a refusé AUTH LOGIN.', $resp)); }
            $send(base64_encode($user));
            $resp = $read(); if ($resp === false || (int)substr($resp,0,3) >= 400) { return $fail(self::formatSmtpError('Identifiant SMTP refusé.', $resp)); }
            $send(base64_encode($pass));
            $resp = $read(); if ($resp === false || (int)substr($resp,0,3) >= 400) { return $fail(self::formatSmtpError('Mot de passe SMTP refusé.', $resp)); }
        }

        // MAIL FROM / RCPT TO
        $send('MAIL FROM: <' . $fromEmail . '>');
        $resp = $read(); if ($resp === false || (int)substr($resp,0,3) >= 400) { return $fail(self::formatSmtpError('Adresse expéditeur refusée par le serveur SMTP.', $resp)); }
        $send('RCPT TO: <' . $toEmail . '>');
        $resp = $read(); if ($resp === false || (int)substr($resp,0,3) >= 400) { return $fail(self::formatSmtpError('Destinataire refusé par le serveur SMTP.', $resp)); }

        // DATA
        $send('DATA');
        $resp = $read(); if ($resp === false || (int)substr($resp,0,3) >= 400) { return $fail(self::formatSmtpError('Le serveur SMTP a refusé le contenu du message.', $resp)); }

        $msg  = 'To: ' . $toHeader . "\r\n";
        $msg .= implode("\r\n", $headers) . "\r\n";
        $msg .= 'Subject: ' . self::encodeHeader($subject) . "\r\n";
        $msg .= "\r\n" . $body . "\r\n";
        $msg .= ".\r\n";
        fwrite($fp, $msg);

        $resp = $read(); if ($resp === false || (int)substr($resp,0,3) >= 400) { return $fail(self::formatSmtpError('Le serveur SMTP n\'a pas accepté le message final.', $resp)); }
        $send('QUIT');
        fclose($fp);
        return ['ok' => true, 'message' => 'Email remis au serveur SMTP.'];
    }

    private static function buildMimeMessage(string $toEmail, string $toName, string $fromEmail, string $fromName, string $subject, string $htmlBody): array
    {
        $plainBody = trim(html_entity_decode(strip_tags(preg_replace('~<br\s*/?>~i', "\n", $htmlBody)), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
        if ($plainBody === '') {
            $plainBody = 'Ceci est un email HTML envoyé par ' . $fromName . '.';
        }

        $boundary = '=_Part_' . bin2hex(random_bytes(12));
        $body = [];
        $body[] = '--' . $boundary;
        $body[] = 'Content-Type: text/plain; charset=UTF-8';
        $body[] = 'Content-Transfer-Encoding: base64';
        $body[] = '';
        $body[] = rtrim(chunk_split(base64_encode(self::normalizeLines($plainBody)), 76, "\r\n"));
        $body[] = '--' . $boundary;
        $body[] = 'Content-Type: text/html; charset=UTF-8';
        $body[] = 'Content-Transfer-Encoding: base64';
        $body[] = '';
        $body[] = rtrim(chunk_split(base64_encode(self::normalizeLines($htmlBody)), 76, "\r\n"));
        $body[] = '--' . $boundary . '--';

        return [
            'boundary' => $boundary,
            'body' => implode("\r\n", $body),
        ];
    }

    private static function buildMessageId(string $fromEmail): string
    {
        $domain = 'localhost';
        if (strpos($fromEmail, '@') !== false) {
            $domain = substr(strrchr($fromEmail, '@'), 1) ?: $domain;
        }

        return bin2hex(random_bytes(12)) . '@' . preg_replace('~[^a-z0-9.-]+~i', '', $domain);
    }

    private static function formatMailbox(string $email, string $name): string
    {
        $name = trim($name);
        return $name !== '' ? (self::encodeHeader($name) . " <{$email}>") : $email;
    }

    private static function formatSmtpError(string $message, $response): string
    {
        $response = trim((string)$response);
        if ($response === '') {
            return $message;
        }

        $friendly = self::friendlySmtpError($response);
        if ($friendly !== null) {
            return $friendly;
        }

        return $message . ' Réponse SMTP: ' . preg_replace('~\s+~', ' ', $response);
    }

    /**
     * Traduit les erreurs SMTP connues en messages lisibles pour l'utilisateur final.
     * Retourne null si l'erreur n'est pas reconnue.
     */
    private static function friendlySmtpError(string $response): ?string
    {
        $r = strtolower($response);

        // Quota journalier dépassé (Gmail 550 5.4.5)
        if (str_contains($r, '5.4.5') || str_contains($r, 'daily') && str_contains($r, 'limit')) {
            return 'Quota journalier d\'envoi dépassé sur ce compte Gmail. '
                 . 'Vous pouvez réessayer demain, ou utiliser un compte de service Google Workspace '
                 . 'avec des limites plus élevées (2 000 emails/jour).';
        }

        // Authentification refusée
        if (str_contains($r, '5.7.8') || str_contains($r, 'username and password') || str_contains($r, 'invalid credentials')) {
            return 'Authentification SMTP refusée : identifiant ou mot de passe incorrect. '
                 . 'Pour Gmail, utilisez un mot de passe d\'application (pas votre mot de passe principal).';
        }

        // Connexion refusée (relaying)
        if (str_contains($r, '5.7.0') || str_contains($r, 'relay')) {
            return 'Le serveur SMTP refuse le relais pour cet expéditeur. '
                 . 'Vérifiez que l\'adresse "De" correspond bien à votre compte Gmail configuré.';
        }

        // Taux d'envoi trop élevé (rate limiting)
        if (str_contains($r, '4.7.') || str_contains($r, 'too many') || str_contains($r, 'rate')) {
            return 'Trop d\'envois en peu de temps (rate limiting). Attendez quelques minutes avant de réessayer.';
        }

        // Destinataire inconnu
        if (str_contains($r, '5.1.1') || str_contains($r, 'user unknown') || str_contains($r, 'no such user')) {
            return 'Adresse email destinataire inconnue ou inexistante.';
        }

        return null;
    }

    private static function normalizeLines(string $body): string
    {
        $body = str_replace(["\r\n","\r"], "\n", $body);
        $body = str_replace("\n", "\r\n", $body);
        // Échapper lignes commençant par un point
        $body = preg_replace('~^(\.)~m', '..', $body);
        return $body;
    }
}
