<?php

declare(strict_types=1);

final class MyronSmtpTransport implements MailTransportInterface
{
    public function __construct(private readonly MyronMailSettingsRepository $settingsRepository)
    {
    }

    public function send(MailEnvelope $envelope): MailDispatchResult
    {
        $settings = $this->settingsRepository->getSettings();
        if (!is_array($settings)) {
            return MailDispatchResult::failed('smtp', 'mail_settings_missing');
        }

        try {
            $capturePath = trim((string) (getenv('MYRON_MAIL_CAPTURE_PATH') ?: ''));
            if ($capturePath !== '') {
                $this->captureMail($capturePath, $envelope);
                return MailDispatchResult::ok('smtp', 'capture-' . bin2hex(random_bytes(4)));
            }

            $this->sendViaSmtp($settings, $envelope);
            return MailDispatchResult::ok('smtp');
        } catch (Throwable $exception) {
            return MailDispatchResult::failed('smtp', $exception->getMessage());
        }
    }

    public function describe(): array
    {
        return [
            'slug' => 'smtp',
            'label' => 'SMTP',
            'description' => 'Core socket SMTP transport using saved Mail Settings.',
            'scopes' => [MyronApiScopes::ADMINISTRATION_READ],
            'capabilities' => ['smtp', 'starttls', 'html', 'text', 'capture'],
        ];
    }

    private function captureMail(string $path, MailEnvelope $envelope): void
    {
        $payload = $envelope->toArray();
        $payload['capturedAt'] = myronNow();
        file_put_contents($path, json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL, FILE_APPEND);
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function sendViaSmtp(array $settings, MailEnvelope $envelope): void
    {
        $host = trim((string) ($settings['smtp_host'] ?? ''));
        $port = (int) ($settings['smtp_port'] ?? 587);
        $security = trim((string) ($settings['smtp_security'] ?? 'tls'));
        $username = trim((string) ($settings['smtp_username'] ?? ''));
        $password = trim((string) ($settings['smtp_password'] ?? ''));
        $fromEmail = trim($envelope->fromEmail);
        $fromName = trim($envelope->fromName);

        if ($host === '' || $username === '' || $password === '' || $fromEmail === '') {
            throw new RuntimeException('Mail settings are incomplete.');
        }

        $transport = $security === 'ssl' ? 'ssl://' . $host : $host;
        $socket = @stream_socket_client($transport . ':' . $port, $errno, $errstr, 5, STREAM_CLIENT_CONNECT);
        if (!is_resource($socket)) {
            throw new RuntimeException('SMTP connection failed: ' . $errstr);
        }

        stream_set_timeout($socket, 5);
        $this->assertResponse($socket, [220]);
        $this->command($socket, 'EHLO myroncms.local', [250]);

        if ($security === 'tls') {
            $this->command($socket, 'STARTTLS', [220]);
            if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                throw new RuntimeException('SMTP STARTTLS negotiation failed.');
            }
            $this->command($socket, 'EHLO myroncms.local', [250]);
        }

        $this->command($socket, 'AUTH LOGIN', [334]);
        $this->command($socket, base64_encode($username), [334]);
        $this->command($socket, base64_encode($password), [235]);
        $this->command($socket, 'MAIL FROM:<' . $fromEmail . '>', [250]);
        $this->command($socket, 'RCPT TO:<' . $envelope->to . '>', [250, 251]);
        $this->command($socket, 'DATA', [354]);

        $boundary = 'myron-' . bin2hex(random_bytes(8));
        $headers = [
            'Date: ' . gmdate('r'),
            'From: ' . $fromName . ' <' . $fromEmail . '>',
            'To: <' . $envelope->to . '>',
            'Subject: ' . $envelope->subject,
            'MIME-Version: 1.0',
            'Content-Type: multipart/alternative; boundary="' . $boundary . '"',
        ];
        if ($envelope->replyTo !== null && filter_var($envelope->replyTo, FILTER_VALIDATE_EMAIL)) {
            $headers[] = 'Reply-To: <' . $envelope->replyTo . '>';
        }
        foreach ($envelope->headers as $name => $value) {
            $headers[] = $name . ': ' . str_replace(["\r", "\n"], '', $value);
        }

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

        fwrite($socket, $message . "\r\n");
        $this->assertResponse($socket, [250]);
        $this->command($socket, 'QUIT', [221]);
        fclose($socket);
    }

    /**
     * @param list<int> $codes
     */
    private function command($socket, string $command, array $codes): void
    {
        fwrite($socket, $command . "\r\n");
        $this->assertResponse($socket, $codes);
    }

    /**
     * @param list<int> $codes
     */
    private function assertResponse($socket, array $codes): void
    {
        $response = '';
        while (($line = fgets($socket, 515)) !== false) {
            $response .= $line;
            if (strlen($line) >= 4 && $line[3] === ' ') {
                break;
            }
        }

        $status = (int) substr($response, 0, 3);
        if (!in_array($status, $codes, true)) {
            throw new RuntimeException('SMTP error: ' . trim($response));
        }
    }
}
