<?php

declare(strict_types=1);

namespace App\Framework\Email;

use App\Domain\Common\ValueObject\Email;
use App\Framework\Core\ValueObjects\EmailAddress;
use App\Framework\Email\ValueObjects\EmailContent;
use App\Framework\Email\ValueObjects\EmailSubject;
use App\Framework\Logging\Logger;
use App\Framework\Mail\MailerInterface;
use App\Framework\Mail\Message;
use App\Framework\Mail\Priority;
use App\Framework\Template\Parser\DomTemplateParser;

/**
 * High-level email service that combines template rendering with sending
 */
final readonly class EmailService
{
    private EmailTemplateRenderer $renderer;

    public function __construct(
        private MailerInterface $mailer,
        private DomTemplateParser $parser,
        private CssInliner $cssInliner,
        private Logger $logger,
        /** @var array<string, string> */
        private array $defaultFrom = []
    ) {
        $this->renderer = new EmailTemplateRenderer($parser, $cssInliner);
    }

    /**
     * Send a typed email using EmailInterface
     */
    public function send(EmailInterface $email): bool
    {
        try {
            // Get default from email
            $fromEmail = $this->getDefaultFromEmail();

            // Convert email to message
            $message = $email->toMessage($this->renderer, $fromEmail);

            // Send via mail transport
            $success = $this->mailer->send($message);

            if ($success) {
                $this->logger->info('Typed email sent successfully', [
                    'type' => get_class($email),
                    'to' => $message->getAllRecipients()->toString(),
                    'subject' => $message->subject,
                ]);

                return true;
            }

            $this->logger->error('Failed to send typed email', [
                'type' => get_class($email),
                'to' => $message->getAllRecipients()->toString(),
                'error' => 'Send operation failed',
            ]);

            return false;

        } catch (\Throwable $e) {
            $this->logger->error('Exception while sending typed email', [
                'type' => get_class($email),
                'exception' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            throw $e;
        }
    }

    /**
     * Get default from email as Email object
     */
    private function getDefaultFromEmail(): \App\Domain\Common\ValueObject\Email
    {
        $emailAddress = $this->defaultFrom['email'] ?? 'noreply@example.com';

        return new \App\Domain\Common\ValueObject\Email($emailAddress);
    }

    /**
     * Send a templated email
     */
    public function sendTemplate(
        string $template,
        EmailAddress|string $to,
        EmailContext $context,
        ?EmailSubject $subject = null,
        ?Priority $priority = null
    ): bool {
        try {
            // Ensure EmailAddress object
            if (is_string($to)) {
                $to = new EmailAddress($to);
            }

            // Render the template
            $content = $this->renderer->render($template, $context);

            // Use provided subject or extracted from template
            $finalSubject = $subject ?? $content->subject;
            if ($finalSubject === null) {
                throw new \RuntimeException('Email subject is required but not provided');
            }

            // Create message
            $message = $this->createMessage($to, $finalSubject, $content, $priority);

            // Send email
            $success = $this->mailer->send($message);

            if ($success) {
                $this->logger->info('Email sent successfully', [
                    'template' => $template,
                    'to' => $to->toString(),
                    'subject' => $finalSubject->value,
                    'size' => $content->getSize()->toHumanReadable(),
                ]);

                return true;
            }

            $this->logger->error('Failed to send email', [
                'template' => $template,
                'to' => $to->toString(),
                'error' => 'Send operation failed',
            ]);

            return false;

        } catch (\Throwable $e) {
            $this->logger->error('Exception while sending email', [
                'template' => $template,
                'to' => is_string($to) ? $to : $to->toString(),
                'exception' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            throw $e;
        }
    }

    /**
     * Send multiple emails using the same template
     *
     * @param EmailAddress[] $recipients
     * @return array<string, array<string>>
     */
    public function sendBulkTemplate(
        string $template,
        array $recipients,
        EmailContext $baseContext,
        ?EmailSubject $subject = null
    ): array {
        $results = [
            'success' => [],
            'failed' => [],
        ];

        foreach ($recipients as $recipient) {
            try {
                // Personalize context for each recipient if needed
                $personalizedContext = $this->personalizeContext($baseContext, $recipient);

                $sent = $this->sendTemplate(
                    $template,
                    $recipient,
                    $personalizedContext,
                    $subject
                );

                if ($sent) {
                    $results['success'][] = $recipient->toString();
                } else {
                    $results['failed'][] = $recipient->toString();
                }

            } catch (\Throwable $e) {
                $results['failed'][] = $recipient->toString();

                $this->logger->error('Failed to send bulk email', [
                    'recipient' => $recipient->toString(),
                    'error' => $e->getMessage(),
                ]);
            }
        }

        $this->logger->info('Bulk email completed', [
            'template' => $template,
            'total' => count($recipients),
            'success' => count($results['success']),
            'failed' => count($results['failed']),
        ]);

        return $results;
    }

    /**
     * Send a welcome email
     */
    public function sendWelcomeEmail(
        EmailAddress $to,
        string $name,
        string $companyName,
        string $loginUrl
    ): bool {
        $context = new EmailContext(
            data: [
                'name' => $name,
                'email' => $to->toString(),
                'company_name' => $companyName,
                'login_url' => $loginUrl,
                'current_year' => date('Y'),
                'facebook_url' => '#',
                'twitter_url' => '#',
                'linkedin_url' => '#',
            ],
            preheader: "Willkommen! Hier sind deine ersten Schritte mit {$companyName}"
        );

        return $this->sendTemplate(
            'welcome',
            $to,
            $context,
            priority: Priority::HIGH
        );
    }

    /**
     * Send a password reset email
     */
    public function sendPasswordResetEmail(
        EmailAddress $to,
        string $name,
        string $resetUrl,
        string $resetCode,
        string $requestIp,
        int $expiryHours = 24
    ): bool {
        $context = new EmailContext(
            data: [
                'name' => $name,
                'company_name' => $this->getCompanyName(),
                'reset_url' => $resetUrl,
                'reset_code' => $resetCode,
                'request_ip' => $requestIp,
                'expiry_hours' => $expiryHours,
                'current_year' => date('Y'),
                'privacy_url' => '/privacy',
                'support_url' => '/support',
            ],
            preheader: "Passwort zurücksetzen - Aktion erforderlich"
        );

        return $this->sendTemplate(
            'password-reset',
            $to,
            $context,
            new EmailSubject('Passwort zurücksetzen - Aktion erforderlich'),
            Priority::HIGHEST
        );
    }

    /**
     * Send a notification email with custom content
     */
    public function sendNotification(
        EmailAddress $to,
        string $subject,
        string $htmlContent,
        ?string $textContent = null
    ): bool {
        try {
            // Create email content directly
            $content = new EmailContent(
                html: $htmlContent,
                text: $textContent ?? $this->generatePlainText($htmlContent),
                subject: new EmailSubject($subject)
            );

            if ($content->subject === null) {
                throw new \RuntimeException('Email subject is required for notifications');
            }

            $message = $this->createMessage(
                $to,
                $content->subject,
                $content,
                Priority::NORMAL
            );

            return $this->mailer->send($message);

        } catch (\Throwable $e) {
            $this->logger->error('Failed to send notification', [
                'to' => $to->toString(),
                'subject' => $subject,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    /**
     * Create a Message object from components
     */
    private function createMessage(
        EmailAddress $to,
        EmailSubject $subject,
        EmailContent $content,
        ?Priority $priority = null
    ): Message {
        $message = new Message();

        // Set from address
        if (! empty($this->defaultFrom)) {
            $message->setFrom(
                $this->defaultFrom['email'] ?? 'noreply@example.com',
                $this->defaultFrom['name'] ?? null
            );
        }

        // Set recipient
        $message->addTo($to->toString());

        // Set subject
        $message->setSubject($subject->value);

        // Set content
        if ($content->hasMultipart()) {
            // Both HTML and text versions
            $message->setHtmlBody($content->html);
            $message->setTextBody($content->text);
        } elseif (! empty($content->html)) {
            // HTML only
            $message->setHtmlBody($content->html);
        } else {
            // Text only
            $message->setTextBody($content->text);
        }

        // Set priority if specified
        if ($priority !== null) {
            $message->setPriority($priority);
        }

        return $message;
    }

    /**
     * Personalize context for a specific recipient
     */
    private function personalizeContext(EmailContext $baseContext, EmailAddress $recipient): EmailContext
    {
        $data = $baseContext->data;

        // Add recipient email to context
        $data['email'] = $recipient->toString();

        // Extract name from email if not set
        if (! isset($data['name']) || empty($data['name'])) {
            $data['name'] = $recipient->getLocalPart();
        }

        return new EmailContext(
            data: $data,
            preheader: $baseContext->preheader,
            unsubscribeUrl: $baseContext->unsubscribeUrl,
            webViewUrl: $baseContext->webViewUrl,
            inlineCss: $baseContext->inlineCss,
            theme: $baseContext->theme,
            trackingId: $baseContext->trackingId,
            utmParams: $baseContext->utmParams
        );
    }

    /**
     * Generate plain text from HTML (fallback)
     */
    private function generatePlainText(string $html): string
    {
        // Remove style and script tags
        $cleaned = preg_replace('/<style[^>]*>.*?<\/style>/si', '', $html);
        $html = $cleaned !== null ? $cleaned : $html;

        $cleaned = preg_replace('/<script[^>]*>.*?<\/script>/si', '', $html);
        $html = $cleaned !== null ? $cleaned : $html;

        // Convert breaks and paragraphs
        $cleaned = preg_replace('/<br\s*\/?>/i', "\n", $html);
        $html = $cleaned !== null ? $cleaned : $html;

        $cleaned = preg_replace('/<\/p>/i', "\n\n", $html);
        $html = $cleaned !== null ? $cleaned : $html;

        // Convert links
        $cleaned = preg_replace('/<a[^>]+href="([^"]*)"[^>]*>(.*?)<\/a>/i', '$2 ($1)', $html);
        $html = $cleaned !== null ? $cleaned : $html;

        // Strip remaining tags
        $text = strip_tags($html);

        // Clean up whitespace
        $cleaned = preg_replace('/\n\s+\n/', "\n\n", $text);
        $text = $cleaned !== null ? $cleaned : $text;

        $cleaned = preg_replace('/\n{3,}/', "\n\n", $text);
        $text = $cleaned !== null ? $cleaned : $text;

        return trim($text);
    }

    /**
     * Get company name from config or default
     */
    private function getCompanyName(): string
    {
        return $this->defaultFrom['company'] ?? 'Our Company';
    }
}
