<?php

class EmailService
{
    public function send(string $to, string $subject, string $htmlBody, string $textBody = '', array $attachments = array()): bool
    {
        $to = trim($to);
        if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Destinatario de email inválido.');
        }

        if ($textBody === '') {
            $textBody = $this->htmlToText($htmlBody);
        }

        $attachments = $this->validateAttachments($attachments);

        if (defined('SMTP_HOST') && trim((string)SMTP_HOST) !== '') {
            return $this->sendSmtp($to, $subject, $htmlBody, $textBody, $attachments);
        }

        return $this->sendMailFunction($to, $subject, $htmlBody, $textBody, $attachments);
    }

    private function sendMailFunction(string $to, string $subject, string $htmlBody, string $textBody, array $attachments): bool
    {
        $fromEmail = defined('NOTIFICATIONS_EMAIL_FROM') && NOTIFICATIONS_EMAIL_FROM !== '' ? NOTIFICATIONS_EMAIL_FROM : 'no-reply@app.icylab.cl';
        $fromName = defined('NOTIFICATIONS_EMAIL_FROM_NAME') && NOTIFICATIONS_EMAIL_FROM_NAME !== '' ? NOTIFICATIONS_EMAIL_FROM_NAME : 'ICYLAB OS';
        $message = $this->buildMimeMessage($to, $subject, $htmlBody, $textBody, $fromEmail, $fromName, $attachments, false);
        $separator = strpos($message, "\r\n\r\n");
        $headers = $separator === false ? $message : substr($message, 0, $separator);
        $body = $separator === false ? '' : substr($message, $separator + 4);
        $ok = mail($to, $this->encodeHeader($subject), $body, $headers);
        if (!$ok) {
            throw new RuntimeException('mail() no pudo aceptar el mensaje.');
        }

        return true;
    }

    private function sendSmtp(string $to, string $subject, string $htmlBody, string $textBody, array $attachments): bool
    {
        $host = trim((string)SMTP_HOST);
        $port = defined('SMTP_PORT') && SMTP_PORT ? (int)SMTP_PORT : 587;
        $secure = defined('SMTP_SECURE') ? strtolower(trim((string)SMTP_SECURE)) : 'tls';
        $timeout = 30;

        $transportHost = ($secure === 'ssl' || $secure === 'smtps' || $port === 465) ? 'ssl://' . $host : $host;
        $errno = 0;
        $errstr = '';
        $socket = stream_socket_client($transportHost . ':' . $port, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT);
        if (!$socket) {
            throw new RuntimeException('No fue posible conectar al SMTP: ' . $errstr);
        }

        stream_set_timeout($socket, $timeout);

        try {
            $this->smtpExpect($socket, [220]);
            $ehloName = defined('SMTP_EHLO_DOMAIN') && SMTP_EHLO_DOMAIN !== '' ? SMTP_EHLO_DOMAIN : 'app.icylab.cl';
            $this->smtpCommand($socket, 'EHLO ' . $ehloName, [250]);

            if ($secure === 'tls' || $secure === 'starttls') {
                $this->smtpCommand($socket, 'STARTTLS', [220]);
                if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                    throw new RuntimeException('No fue posible iniciar TLS con el servidor SMTP.');
                }
                $this->smtpCommand($socket, 'EHLO ' . $ehloName, [250]);
            }

            $username = defined('SMTP_USERNAME') ? trim((string)SMTP_USERNAME) : '';
            $password = defined('SMTP_PASSWORD') ? (string)SMTP_PASSWORD : '';
            if ($username !== '') {
                $this->smtpCommand($socket, 'AUTH LOGIN', [334]);
                $this->smtpCommand($socket, base64_encode($username), [334]);
                $this->smtpCommand($socket, base64_encode($password), [235]);
            }

            $fromEmail = defined('NOTIFICATIONS_EMAIL_FROM') && NOTIFICATIONS_EMAIL_FROM !== '' ? NOTIFICATIONS_EMAIL_FROM : $username;
            if ($fromEmail === '') {
                throw new RuntimeException('Falta configurar NOTIFICATIONS_EMAIL_FROM o SMTP_USERNAME.');
            }

            $fromName = defined('NOTIFICATIONS_EMAIL_FROM_NAME') && NOTIFICATIONS_EMAIL_FROM_NAME !== '' ? NOTIFICATIONS_EMAIL_FROM_NAME : 'ICYLAB OS';
            $message = $this->buildMimeMessage($to, $subject, $htmlBody, $textBody, $fromEmail, $fromName, $attachments, true);

            $this->smtpCommand($socket, 'MAIL FROM:<' . $fromEmail . '>', [250]);
            $this->smtpCommand($socket, 'RCPT TO:<' . $to . '>', [250, 251]);
            $this->smtpCommand($socket, 'DATA', [354]);
            fwrite($socket, $message . "\r\n.\r\n");
            $this->smtpExpect($socket, [250]);
            $this->smtpCommand($socket, 'QUIT', [221]);
        } finally {
            fclose($socket);
        }

        return true;
    }

    private function buildMimeMessage(string $to, string $subject, string $htmlBody, string $textBody, string $fromEmail, string $fromName, array $attachments, bool $includeEnvelopeHeaders): string
    {
        $mixedBoundary = 'mx_' . bin2hex(random_bytes(12));
        $altBoundary = 'alt_' . bin2hex(random_bytes(12));

        $headers = [];
        if ($includeEnvelopeHeaders) {
            $headers[] = 'Date: ' . date(DATE_RFC2822);
            $headers[] = 'To: <' . $to . '>';
            $headers[] = 'Subject: ' . $this->encodeHeader($subject);
        }
        $headers[] = 'From: ' . $this->encodeHeader($fromName) . ' <' . $fromEmail . '>';
        $headers[] = 'MIME-Version: 1.0';
        $headers[] = 'Content-Type: multipart/mixed; boundary="' . $mixedBoundary . '"';

        $body = implode("\r\n", $headers) . "\r\n\r\n";
        $body .= '--' . $mixedBoundary . "\r\n";
        $body .= 'Content-Type: multipart/alternative; boundary="' . $altBoundary . "\"\r\n\r\n";
        $body .= '--' . $altBoundary . "\r\n";
        $body .= "Content-Type: text/plain; charset=UTF-8\r\n";
        $body .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
        $body .= $this->dotEscape($textBody) . "\r\n\r\n";
        $body .= '--' . $altBoundary . "\r\n";
        $body .= "Content-Type: text/html; charset=UTF-8\r\n";
        $body .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
        $body .= $this->dotEscape($htmlBody) . "\r\n\r\n";
        $body .= '--' . $altBoundary . "--\r\n";

        foreach ($attachments as $attachment) {
            $content = file_get_contents($attachment['path']);
            if ($content === false) throw new RuntimeException('No fue posible leer el archivo adjunto.');
            $name = $this->safeFilename($attachment['name']);
            $body .= '--' . $mixedBoundary . "\r\n";
            $body .= 'Content-Type: ' . $attachment['mime'] . '; name="' . $name . "\"\r\n";
            $body .= "Content-Transfer-Encoding: base64\r\n";
            $body .= 'Content-Disposition: attachment; filename="' . $name . "\"\r\n\r\n";
            $body .= chunk_split(base64_encode($content)) . "\r\n";
        }
        $body .= '--' . $mixedBoundary . '--';

        return $body;
    }

    private function validateAttachments(array $attachments): array
    {
        $validated = array();
        foreach ($attachments as $attachment) {
            if (!is_array($attachment)) continue;
            $path = (string)($attachment['path'] ?? '');
            if ($path === '' || !is_file($path) || !is_readable($path)) throw new RuntimeException('Archivo adjunto no disponible.');
            if (filesize($path) > 15 * 1024 * 1024) throw new RuntimeException('El archivo adjunto supera 15 MB.');
            $validated[] = array(
                'path' => $path,
                'name' => $this->safeFilename((string)($attachment['name'] ?? basename($path))),
                'mime' => preg_match('/^[A-Za-z0-9.+-]+\/[A-Za-z0-9.+-]+$/', (string)($attachment['mime'] ?? '')) ? (string)$attachment['mime'] : 'application/octet-stream',
            );
        }
        return $validated;
    }

    private function safeFilename(string $name): string
    {
        $name = str_replace(array("\r", "\n", '"', '\\'), array('', '', '', '_'), $name);
        return $name !== '' ? $name : 'archivo';
    }

    private function smtpCommand($socket, string $command, array $expectedCodes): string
    {
        fwrite($socket, $command . "\r\n");
        return $this->smtpExpect($socket, $expectedCodes);
    }

    private function smtpExpect($socket, array $expectedCodes): string
    {
        $response = '';
        do {
            $line = fgets($socket, 515);
            if ($line === false) {
                throw new RuntimeException('Respuesta SMTP vacía o conexión cerrada.');
            }
            $response .= $line;
            $code = (int)substr($line, 0, 3);
            $more = isset($line[3]) && $line[3] === '-';
        } while ($more);

        if (!in_array($code, $expectedCodes, true)) {
            throw new RuntimeException('Respuesta SMTP inesperada: ' . trim($response));
        }

        return $response;
    }

    private function htmlToText(string $html): string
    {
        $html = preg_replace('/<br\s*\/?>/i', "\n", $html);
        $html = preg_replace('/<\/p>/i', "\n\n", $html);
        return trim(html_entity_decode(strip_tags($html), ENT_QUOTES, 'UTF-8'));
    }

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

    private function dotEscape(string $body): string
    {
        return preg_replace('/^\./m', '..', $body);
    }
}
