<?php

declare(strict_types=1);

final class MailService
{
    private array $config;
    private string $lastError = '';

    public function __construct(array $config)
    {
        $this->config = $config;
    }

    public function getLastError(): string
    {
        return $this->lastError;
    }

    public function sendHtml(string $to, string $subject, string $html): bool
    {
        $this->lastError = '';
        if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
            $this->lastError = 'Dirección de destino inválida.';
            return false;
        }

        $transport = strtolower(trim((string) ($this->config['transport'] ?? 'auto')));
        $hasSmtp = trim((string) ($this->config['host'] ?? '')) !== '';

        if ($transport === 'smtp' || ($transport === 'auto' && $hasSmtp)) {
            if ($this->sendSmtp($to, $subject, $html)) return true;
            if ($transport === 'smtp') return false;
        }

        if ($transport === 'mail' || $transport === 'auto') {
            return $this->sendNativeMail($to, $subject, $html);
        }

        $this->lastError = 'Transporte de correo no compatible.';
        return false;
    }

    private function sendNativeMail(string $to, string $subject, string $html): bool
    {
        if (!function_exists('mail')) {
            $this->lastError = 'La función mail() no está disponible.';
            return false;
        }

        $fromEmail = $this->fromEmail();
        $fromName = $this->encodeHeader((string) ($this->config['from_name'] ?? 'Ave Fénix'));
        $headers = [
            'MIME-Version: 1.0',
            'Content-Type: text/html; charset=UTF-8',
            'Content-Transfer-Encoding: 8bit',
            'From: ' . $fromName . ' <' . $fromEmail . '>',
            'Reply-To: ' . $fromEmail,
            'X-Mailer: Ave-Fenix-MailService',
        ];

        $ok = @mail($to, $this->encodeHeader($subject), $html, implode("\r\n", $headers));
        if (!$ok) $this->lastError = 'El servidor no aceptó el mensaje mediante mail().';
        return $ok;
    }

    private function sendSmtp(string $to, string $subject, string $html): bool
    {
        $host = trim((string) ($this->config['host'] ?? ''));
        $port = max(1, (int) ($this->config['port'] ?? 587));
        $encryption = strtolower(trim((string) ($this->config['encryption'] ?? 'tls')));
        $timeout = max(5, (int) ($this->config['timeout'] ?? 15));
        $verifyPeer = (bool) ($this->config['verify_peer'] ?? true);
        $remote = ($encryption === 'ssl' ? 'ssl://' : 'tcp://') . $host . ':' . $port;
        $context = stream_context_create([
            'ssl' => [
                'verify_peer' => $verifyPeer,
                'verify_peer_name' => $verifyPeer,
                'allow_self_signed' => !$verifyPeer,
                'peer_name' => $host,
                'SNI_enabled' => true,
            ],
        ]);

        $socket = @stream_socket_client($remote, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $context);
        if (!is_resource($socket)) {
            $this->lastError = 'No fue posible conectar con el servidor SMTP.';
            return false;
        }

        stream_set_timeout($socket, $timeout);
        try {
            $this->expect($socket, [220]);
            $hostname = preg_replace('/[^A-Za-z0-9.-]/', '', (string) ($_SERVER['SERVER_NAME'] ?? 'localhost')) ?: 'localhost';
            $this->command($socket, 'EHLO ' . $hostname, [250]);

            if ($encryption === 'tls') {
                $this->command($socket, 'STARTTLS', [220]);
                $cryptoOk = @stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
                if ($cryptoOk !== true) throw new RuntimeException('No fue posible iniciar el cifrado TLS.');
                $this->command($socket, 'EHLO ' . $hostname, [250]);
            }

            $username = (string) ($this->config['username'] ?? '');
            $password = (string) ($this->config['password'] ?? '');
            if ($username !== '') {
                $this->command($socket, 'AUTH LOGIN', [334]);
                $this->command($socket, base64_encode($username), [334]);
                $this->command($socket, base64_encode($password), [235]);
            }

            $fromEmail = $this->fromEmail();
            $this->command($socket, 'MAIL FROM:<' . $fromEmail . '>', [250]);
            $this->command($socket, 'RCPT TO:<' . $to . '>', [250, 251]);
            $this->command($socket, 'DATA', [354]);

            $messageIdHost = preg_replace('/[^A-Za-z0-9.-]/', '', $host) ?: 'localhost';
            $headers = [
                'Date: ' . date(DATE_RFC2822),
                'Message-ID: <' . bin2hex(random_bytes(12)) . '@' . $messageIdHost . '>',
                'From: ' . $this->encodeHeader((string) ($this->config['from_name'] ?? 'Ave Fénix')) . ' <' . $fromEmail . '>',
                'To: <' . $to . '>',
                'Subject: ' . $this->encodeHeader($subject),
                'MIME-Version: 1.0',
                'Content-Type: text/html; charset=UTF-8',
                'Content-Transfer-Encoding: 8bit',
            ];
            $body = preg_replace('/(?m)^\./', '..', str_replace(["\r\n", "\r"], "\n", $html));
            $payload = implode("\r\n", $headers) . "\r\n\r\n" . str_replace("\n", "\r\n", (string) $body) . "\r\n.";
            $this->command($socket, $payload, [250]);
            @fwrite($socket, "QUIT\r\n");
            fclose($socket);
            return true;
        } catch (Throwable $e) {
            $this->lastError = $e->getMessage();
            @fwrite($socket, "QUIT\r\n");
            fclose($socket);
            return false;
        }
    }

    private function fromEmail(): string
    {
        $from = trim((string) ($this->config['from_email'] ?? ''));
        if (filter_var($from, FILTER_VALIDATE_EMAIL)) return $from;
        $host = preg_replace('/:\d+$/', '', (string) ($_SERVER['HTTP_HOST'] ?? 'localhost'));
        $host = preg_replace('/^www\./i', '', $host);
        return 'no-reply@' . ($host ?: 'localhost');
    }

    private function encodeHeader(string $value): string
    {
        return '=?UTF-8?B?' . base64_encode($value) . '?=';
    }

    /** @param resource $socket */
    private function command($socket, string $command, array $expected): string
    {
        if (@fwrite($socket, $command . "\r\n") === false) throw new RuntimeException('No fue posible enviar un comando al servidor SMTP.');
        return $this->expect($socket, $expected);
    }

    /** @param resource $socket */
    private function expect($socket, array $expected): string
    {
        $response = '';
        while (($line = fgets($socket, 515)) !== false) {
            $response .= $line;
            if (strlen($line) < 4 || $line[3] === ' ') break;
        }
        $code = (int) substr($response, 0, 3);
        if (!in_array($code, $expected, true)) {
            throw new RuntimeException('El servidor SMTP rechazó la operación con el código ' . $code . '.');
        }
        return $response;
    }
}
