<?php

declare(strict_types=1);

namespace ABS\Modules\Tasks\Services;

use RuntimeException;

final class TaskSmtpService
{
    public function __construct(
        private readonly string $keyFile
    ) {
    }

    public function send(
        array $settings,
        string $to,
        string $subject,
        string $htmlBody,
        string $textBody=''
    ): void {
        $host = trim((string) ($settings['host'] ?? ''));
        $port = (int) ($settings['port'] ?? 587);
        $encryption = strtolower(trim((string) ($settings['encryption'] ?? 'tls')));
        $username = trim((string) ($settings['username'] ?? ''));
        $password = $this->decrypt((string) ($settings['password_enc'] ?? ''));
        $fromEmail = trim((string) ($settings['from_email'] ?? ''));
        $fromName = trim((string) ($settings['from_name'] ?? 'ADANY IMPEX SRL'));
        $replyTo = trim((string) ($settings['reply_to'] ?? ''));

        if ($host === '' || $fromEmail === '') {
            throw new RuntimeException('Configurația SMTP este incompletă.');
        }

        $remote = ($encryption === 'ssl' ? 'ssl://' : '') . $host . ':' . $port;
        $verifySsl = (int) ($settings['ssl_verify'] ?? 1) === 1;

        $context = stream_context_create([
            'ssl' => [
                'verify_peer' => $verifySsl,
                'verify_peer_name' => $verifySsl,
                'allow_self_signed' => !$verifySsl,
                'SNI_enabled' => true,
                'peer_name' => $host,
                'capture_peer_cert' => true,
                'capture_peer_cert_chain' => true,
            ],
        ]);

        $socket = @stream_socket_client(
            $remote,
            $errno,
            $errstr,
            20,
            STREAM_CLIENT_CONNECT,
            $context
        );

        if (!is_resource($socket)) {
            $lastError = error_get_last();
            $detail = trim((string) $errstr);

            if ($detail === '' && is_array($lastError)) {
                $detail = trim((string) ($lastError['message'] ?? ''));
            }

            if ($detail === '') {
                $detail = 'Fără detalii suplimentare de la PHP/OpenSSL.';
            }

            throw new RuntimeException(
                "Etapa: conectare SSL/TCP\n"
                . "Server: {$host}\n"
                . "Port: {$port}\n"
                . "Criptare: {$encryption}\n"
                . "Verificare certificat: " . ($verifySsl ? 'ON' : 'OFF') . "\n"
                . "Eroare: {$detail}\n"
                . "Cod: {$errno}"
            );
        }

        stream_set_timeout($socket, 20);

        try {
            try {
                $this->expect($socket, [220]);
            } catch (\Throwable $e) {
                throw new RuntimeException('Etapa: banner SMTP. ' . $e->getMessage(), 0, $e);
            }

            $hostname = gethostname() ?: 'localhost';

            try {
                $this->command($socket, 'EHLO ' . $hostname, [250]);
            } catch (\Throwable $e) {
                throw new RuntimeException('Etapa: EHLO. ' . $e->getMessage(), 0, $e);
            }

            if ($encryption === 'tls') {
                $this->command($socket, 'STARTTLS', [220]);

                if (!stream_socket_enable_crypto(
                    $socket,
                    true,
                    STREAM_CRYPTO_METHOD_TLS_CLIENT
                )) {
                    throw new RuntimeException('Negocierea TLS cu serverul SMTP a eșuat.');
                }

                $this->command($socket, 'EHLO ' . $hostname, [250]);
            }

            if ($username !== '') {
                try {
                    $this->command($socket, 'AUTH LOGIN', [334]);
                    $this->command($socket, base64_encode($username), [334]);
                    $this->command($socket, base64_encode($password), [235]);
                } catch (\Throwable $e) {
                    throw new RuntimeException(
                        'Etapa: autentificare SMTP pentru ' . $username . '. ' . $e->getMessage(),
                        0,
                        $e
                    );
                }
            }

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

            $boundary = 'task_' . bin2hex(random_bytes(8));
            $encodedSubject = '=?UTF-8?B?' . base64_encode($subject) . '?=';

            $headers = [
                'Date: ' . date(DATE_RFC2822),
                'From: ' . $this->headerName($fromName) . ' <' . $fromEmail . '>',
                'To: <' . $to . '>',
                'Subject: ' . $encodedSubject,
                'MIME-Version: 1.0',
                'Content-Type: multipart/alternative; boundary="' . $boundary . '"',
            ];

            if ($replyTo !== '') {
                $headers[] = 'Reply-To: <' . $replyTo . '>';
            }

            $message = implode("\r\n", $headers)
                . "\r\n\r\n"
                . '--' . $boundary . "\r\n"
                . "Content-Type: text/plain; charset=UTF-8\r\n"
                . "Content-Transfer-Encoding: 8bit\r\n\r\n"
                . ($textBody !== '' ? $textBody : strip_tags($htmlBody))
                . "\r\n\r\n"
                . '--' . $boundary . "\r\n"
                . "Content-Type: text/html; charset=UTF-8\r\n"
                . "Content-Transfer-Encoding: 8bit\r\n\r\n"
                . $htmlBody
                . "\r\n\r\n"
                . '--' . $boundary . "--\r\n";

            $message = preg_replace('/^\./m', '..', $message) ?? $message;
            fwrite($socket, $message . "\r\n.\r\n");
            $this->expect($socket, [250]);

            $this->command($socket, 'QUIT', [221]);
        } finally {
            fclose($socket);
        }
    }

    private function decrypt(string $encrypted): string
    {
        if ($encrypted === '') {
            return '';
        }

        if (!is_file($this->keyFile)) {
            throw new RuntimeException('Cheia locală SMTP lipsește.');
        }

        $key = base64_decode((string) file_get_contents($this->keyFile), true);
        $payload = base64_decode($encrypted, true);

        if (
            $key === false
            || strlen($key) !== 32
            || $payload === false
            || strlen($payload) < 29
        ) {
            throw new RuntimeException('Parola SMTP criptată nu este validă.');
        }

        $iv = substr($payload, 0, 12);
        $tag = substr($payload, 12, 16);
        $cipher = substr($payload, 28);

        $plain = openssl_decrypt(
            $cipher,
            'aes-256-gcm',
            $key,
            OPENSSL_RAW_DATA,
            $iv,
            $tag
        );

        if ($plain === false) {
            throw new RuntimeException('Parola SMTP nu a putut fi decriptată.');
        }

        return $plain;
    }

    private function command($socket, string $command, array $expected): string
    {
        fwrite($socket, $command . "\r\n");

        return $this->expect($socket, $expected);
    }

    private function expect($socket, array $expected): string
    {
        $response = '';

        while (($line = fgets($socket, 515)) !== false) {
            $response .= $line;

            if (strlen($line) >= 4 && $line[3] === ' ') {
                break;
            }
        }

        if ($response === '') {
            throw new RuntimeException('Serverul SMTP nu a răspuns.');
        }

        $code = (int) substr($response, 0, 3);

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

        return $response;
    }

    private function headerName(string $name): string
    {
        if ($name === '') {
            return '';
        }

        return '=?UTF-8?B?' . base64_encode($name) . '?=';
    }
}
