<?php

declare(strict_types=1);

final class BlogCommentNotificationDispatcher
{
    public function __construct(
        private readonly BlogCommentSettingsRepository $settings,
        private readonly NotificationEmailsRepository $notificationEmails,
        private readonly MyronMailSettingsRepository $mailSettings,
        private readonly MyronContentRepository $content,
        private readonly BlogCommentModerationEventRepository $events,
    ) {
    }

    public function dispatch(array $comment): MailDispatchResult
    {
        $commentId = trim((string) ($comment['id'] ?? ''));
        if ($commentId === '') {
            return MailDispatchResult::failed('smtp', 'comment_not_found');
        }

        $destinationId = trim((string) ($this->settings->get()['notification_email_id'] ?? ''));
        if ($destinationId === '') {
            return MailDispatchResult::ok('disabled');
        }

        $destination = $this->notificationEmails->findById($destinationId);
        if (!is_array($destination) || empty($destination['verified_at'])) {
            $this->record($commentId, 'notification_failed', 'destination_unavailable');
            return MailDispatchResult::failed('smtp', 'destination_unavailable');
        }

        $settings = $this->mailSettings->getSettings();
        if (!is_array($settings)) {
            $this->record($commentId, 'notification_failed', 'mail_settings_missing');
            return MailDispatchResult::failed('smtp', 'mail_settings_missing');
        }

        $envelope = $this->buildEnvelope($comment, $destination, $settings);
        $started = microtime(true);
        try {
            $result = myronMailTransportRegistry()->activeTransport()->send($envelope);
        } catch (Throwable $exception) {
            $result = MailDispatchResult::failed('smtp', $exception->getMessage());
        }

        if ((microtime(true) - $started) > 5.0 && $result->ok) {
            $result = MailDispatchResult::failed('smtp', 'timeout');
        }

        $this->record(
            $commentId,
            $result->ok ? 'notification_sent' : 'notification_failed',
            $result->ok ? (string) ($destination['address'] ?? '') : ($result->error ?? 'dispatch_failed')
        );

        return $result;
    }

    /**
     * @param array<string, mixed> $comment
     * @param array<string, mixed> $destination
     * @param array<string, mixed> $settings
     */
    private function buildEnvelope(array $comment, array $destination, array $settings): MailEnvelope
    {
        $page = $this->content->getPublishedPageById((string) ($comment['page_id'] ?? ''));
        if (!is_array($page)) {
            $page = $this->findPageFallback((string) ($comment['page_id'] ?? ''));
        }

        $pageTitle = trim((string) ($page['title'] ?? 'Untitled post')) ?: 'Untitled post';
        $pagePath = trim((string) ($page['path'] ?? ''));
        $authorName = trim((string) ($comment['author_name'] ?? 'Reader')) ?: 'Reader';
        $authorEmail = myronNormalizeEmail((string) ($comment['author_email'] ?? ''));
        $body = trim((string) ($comment['body'] ?? ''));
        $moderationUrl = myronBuildAdminUrl('content.comments', [
            'status' => BlogCommentRepository::STATUS_PENDING,
            'comment' => (string) ($comment['id'] ?? ''),
        ]);

        $lines = [
            'A new blog comment is waiting for moderation.',
            '',
            'Post: ' . $pageTitle,
            'Path: ' . ($pagePath !== '' ? $pagePath : '-'),
            'Commenter: ' . $authorName,
            'Commenter email: ' . ($authorEmail !== '' ? $authorEmail : '-'),
            '',
            'Moderation: Review this comment before approving, rejecting, marking as spam, or deleting it.',
            'Review link: ' . $moderationUrl,
        ];

        if ($body !== '') {
            $lines[] = '';
            $lines[] = 'Comment preview:';
            $lines[] = $body;
        }

        $textBody = implode("\n", $lines);
        $htmlBody = '<p>A new blog comment is waiting for moderation.</p>'
            . '<dl>'
            . '<dt>Post</dt><dd>' . htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') . '</dd>'
            . '<dt>Path</dt><dd>' . htmlspecialchars($pagePath !== '' ? $pagePath : '-', ENT_QUOTES, 'UTF-8') . '</dd>'
            . '<dt>Commenter</dt><dd>' . htmlspecialchars($authorName, ENT_QUOTES, 'UTF-8') . '</dd>'
            . '<dt>Commenter email</dt><dd>' . htmlspecialchars($authorEmail !== '' ? $authorEmail : '-', ENT_QUOTES, 'UTF-8') . '</dd>'
            . '</dl>'
            . '<p>Review this comment before approving, rejecting, marking as spam, or deleting it.</p>'
            . '<p><a href="' . htmlspecialchars($moderationUrl, ENT_QUOTES, 'UTF-8') . '">Open moderation queue</a></p>';

        if ($body !== '') {
            $htmlBody .= '<h2>Comment preview</h2><p>' . nl2br(htmlspecialchars($body, ENT_QUOTES, 'UTF-8')) . '</p>';
        }

        return new MailEnvelope(
            to: (string) ($destination['address'] ?? ''),
            subject: 'New comment awaiting review: ' . $pageTitle,
            htmlBody: $htmlBody,
            textBody: $textBody,
            fromEmail: (string) ($settings['from_email'] ?? ''),
            fromName: (string) ($settings['from_name'] ?? 'MyronCMS'),
            replyTo: filter_var($authorEmail, FILTER_VALIDATE_EMAIL) ? $authorEmail : null,
            headers: ['X-Myron-Context' => 'blog-comment-moderation']
        );
    }

    private function record(string $commentId, string $action, string $reason): void
    {
        try {
            $this->events->record([
                'comment_id' => $commentId,
                'action' => $action,
                'from_status' => BlogCommentRepository::STATUS_PENDING,
                'to_status' => BlogCommentRepository::STATUS_PENDING,
                'reason' => substr($reason, 0, 500),
                'actor_id' => null,
            ]);
        } catch (Throwable) {
        }
    }

    /**
     * @return array<string, mixed>
     */
    private function findPageFallback(string $pageId): array
    {
        foreach ($this->content->listPages() as $page) {
            if ((string) ($page['id'] ?? '') === $pageId) {
                return $page;
            }
        }

        return [];
    }
}
