<?php

declare(strict_types=1);

namespace App\Modules\Email;

use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\EmailMailboxRepository;
use App\Modules\Settings\EmailTemplateRepository;
use PDO;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception as PHPMailerException;
use RuntimeException;
use Throwable;

final class EmailSendingService
{
    public function __construct(
        private readonly PDO $pdo,
        private readonly OrdersRepository $orders,
        private readonly EmailTemplateRepository $templates,
        private readonly EmailMailboxRepository $mailboxes,
        private readonly VariableResolver $variableResolver,
        private readonly AttachmentGenerator $attachmentGenerator
    ) {
    }

    /**
     * @return array{success: bool, error: ?string, log_id: int}
     */
    public function send(int $orderId, int $templateId, ?int $mailboxId = null, ?string $actorName = null, ?string $recipientEmailOverride = null, ?string $recipientNameOverride = null): array
    {
        $details = $this->orders->findDetails($orderId);
        if ($details === null) {
            return ['success' => false, 'error' => 'Zamowienie nie znalezione', 'log_id' => 0];
        }

        $order = is_array($details['order'] ?? null) ? $details['order'] : [];
        $addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : [];

        $template = $this->templates->findById($templateId);
        if ($template === null) {
            return ['success' => false, 'error' => 'Szablon nie znaleziony', 'log_id' => 0];
        }

        $mailbox = $this->resolveMailbox($mailboxId, $template);
        if ($mailbox === null) {
            return ['success' => false, 'error' => 'Brak skonfigurowanej skrzynki SMTP', 'log_id' => 0];
        }

        $recipientEmail = $recipientEmailOverride !== null && $recipientEmailOverride !== ''
            ? $recipientEmailOverride
            : $this->findRecipientEmail($addresses);
        if ($recipientEmail === '') {
            return ['success' => false, 'error' => 'Brak adresu e-mail odbiorcy', 'log_id' => 0];
        }

        $recipientName = $recipientNameOverride !== null && $recipientNameOverride !== ''
            ? $recipientNameOverride
            : $this->findRecipientName($addresses);
        $companySettings = $this->loadCompanySettings();

        $variableMap = $this->variableResolver->buildVariableMap($order, $addresses, $companySettings);
        $resolvedSubject = $this->variableResolver->resolve((string) ($template['subject'] ?? ''), $variableMap);
        $resolvedBody = $this->variableResolver->resolve((string) ($template['body_html'] ?? ''), $variableMap);
        $resolvedBody = $this->composeBody($resolvedBody, $mailbox, $variableMap);

        $attachments = [];
        $attachmentType = (string) ($template['attachment_1'] ?? '');
        if ($attachmentType !== '') {
            $attachment = $this->attachmentGenerator->generate($attachmentType, $order);
            if ($attachment !== null) {
                $attachments[] = $attachment;
            }
        }

        $status = 'sent';
        $errorMessage = null;
        $sentAt = null;

        try {
            $this->sendViaSMTP($mailbox, $recipientEmail, $recipientName, $resolvedSubject, $resolvedBody, $attachments);
            $sentAt = date('Y-m-d H:i:s');
        } catch (Throwable $e) {
            $status = 'failed';
            $errorMessage = $e->getMessage();
        }

        $logId = $this->logEmail(
            $templateId,
            (int) ($mailbox['id'] ?? 0),
            $orderId,
            $recipientEmail,
            $recipientName,
            $resolvedSubject,
            $resolvedBody,
            $attachments,
            $status,
            $errorMessage,
            $sentAt
        );

        $templateName = (string) ($template['name'] ?? '');
        $activitySummary = $status === 'sent'
            ? 'Wyslano e-mail "' . $resolvedSubject . '" do ' . $recipientEmail
            : 'Blad wysylki e-mail "' . $resolvedSubject . '" do ' . $recipientEmail . ': ' . ($errorMessage ?? '');
        $this->orders->recordActivity(
            $orderId,
            'email_' . $status,
            $activitySummary,
            ['template' => $templateName, 'recipient' => $recipientEmail, 'log_id' => $logId],
            'user',
            $actorName
        );

        return [
            'success' => $status === 'sent',
            'error' => $errorMessage,
            'log_id' => $logId,
        ];
    }

    /**
     * @return array{subject: string, body_html: string, attachments: list<string>}
     */
    public function preview(int $orderId, int $templateId): array
    {
        $details = $this->orders->findDetails($orderId);
        if ($details === null) {
            return ['subject' => '', 'body_html' => '<p>Zamowienie nie znalezione</p>', 'attachments' => []];
        }

        $order = is_array($details['order'] ?? null) ? $details['order'] : [];
        $addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : [];

        $template = $this->templates->findById($templateId);
        if ($template === null) {
            return ['subject' => '', 'body_html' => '<p>Szablon nie znaleziony</p>', 'attachments' => []];
        }

        $companySettings = $this->loadCompanySettings();
        $variableMap = $this->variableResolver->buildVariableMap($order, $addresses, $companySettings);
        $resolvedSubject = $this->variableResolver->resolve((string) ($template['subject'] ?? ''), $variableMap);
        $resolvedBody = $this->variableResolver->resolve((string) ($template['body_html'] ?? ''), $variableMap);

        $mailbox = $this->resolveMailbox(null, $template);
        $resolvedBody = $this->composeBody($resolvedBody, $mailbox, $variableMap);

        $attachmentNames = [];
        $attachmentType = (string) ($template['attachment_1'] ?? '');
        if ($attachmentType !== '') {
            $attachment = $this->attachmentGenerator->generate($attachmentType, $order);
            if ($attachment !== null) {
                $attachmentNames[] = $attachment['filename'];
            }
        }

        return [
            'subject' => $resolvedSubject,
            'body_html' => $resolvedBody,
            'attachments' => $attachmentNames,
        ];
    }

    /**
     * @param array<string, mixed>|null $mailbox
     * @param array<string, string> $variableMap
     */
    private function composeBody(string $resolvedBody, ?array $mailbox, array $variableMap): string
    {
        if ($mailbox === null) {
            return $resolvedBody;
        }

        $header = trim((string) ($mailbox['header_html'] ?? ''));
        $footer = trim((string) ($mailbox['footer_html'] ?? ''));

        if ($header !== '') {
            $header = $this->variableResolver->resolve($header, $variableMap);
        }
        if ($footer !== '') {
            $footer = $this->variableResolver->resolve($footer, $variableMap);
        }

        $parts = [];
        if ($header !== '') {
            $parts[] = $header;
        }
        $parts[] = $resolvedBody;
        if ($footer !== '') {
            $parts[] = $footer;
        }

        return implode("\n", $parts);
    }

    /**
     * @param array<string, mixed>|null $template
     * @return array<string, mixed>|null
     */
    private function resolveMailbox(?int $mailboxId, ?array $template): ?array
    {
        if ($mailboxId !== null && $mailboxId > 0) {
            $mailbox = $this->mailboxes->findById($mailboxId);
            if ($mailbox !== null && (int) ($mailbox['is_active'] ?? 0) === 1) {
                return $mailbox;
            }
        }

        $templateMailboxId = (int) ($template['mailbox_id'] ?? 0);
        if ($templateMailboxId > 0) {
            $mailbox = $this->mailboxes->findById($templateMailboxId);
            if ($mailbox !== null && (int) ($mailbox['is_active'] ?? 0) === 1) {
                return $mailbox;
            }
        }

        $active = $this->mailboxes->listActive();
        foreach ($active as $m) {
            if ((int) ($m['is_default'] ?? 0) === 1) {
                return $this->mailboxes->findById((int) $m['id']);
            }
        }

        return $active !== [] ? $this->mailboxes->findById((int) $active[0]['id']) : null;
    }

    /**
     * @param array<int, array<string, mixed>> $addresses
     */
    private function findRecipientEmail(array $addresses): string
    {
        foreach (['customer', 'delivery', 'invoice'] as $type) {
            foreach ($addresses as $addr) {
                if (($addr['address_type'] ?? '') === $type) {
                    $email = trim((string) ($addr['email'] ?? ''));
                    if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL) !== false) {
                        return $email;
                    }
                }
            }
        }

        return '';
    }

    /**
     * @param array<int, array<string, mixed>> $addresses
     */
    private function findRecipientName(array $addresses): string
    {
        foreach (['customer', 'delivery'] as $type) {
            foreach ($addresses as $addr) {
                if (($addr['address_type'] ?? '') === $type) {
                    $name = trim((string) ($addr['name'] ?? ''));
                    if ($name !== '') {
                        return $name;
                    }
                }
            }
        }

        return '';
    }

    /**
     * @return array<string, mixed>
     */
    private function loadCompanySettings(): array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM company_settings LIMIT 1');
        $stmt->execute();
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return is_array($row) ? $row : [];
    }

    /**
     * @param array<string, mixed> $mailbox
     * @param list<array{filename: string, content: string, mime: string}> $attachments
     */
    private function sendViaSMTP(
        array $mailbox,
        string $recipientEmail,
        string $recipientName,
        string $subject,
        string $body,
        array $attachments
    ): void {
        $mail = new PHPMailer(true);

        $mail->isSMTP();
        $mail->Host = (string) ($mailbox['smtp_host'] ?? '');
        $mail->Port = (int) ($mailbox['smtp_port'] ?? 587);
        $mail->SMTPAuth = true;
        $mail->Username = (string) ($mailbox['smtp_username'] ?? '');
        $mail->Password = (string) ($mailbox['smtp_password_decrypted'] ?? '');
        $mail->CharSet = PHPMailer::CHARSET_UTF8;

        $encryption = (string) ($mailbox['smtp_encryption'] ?? 'tls');
        if ($encryption === 'tls') {
            $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
        } elseif ($encryption === 'ssl') {
            $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
        } else {
            $mail->SMTPSecure = '';
            $mail->SMTPAutoTLS = false;
        }

        $senderEmail = (string) ($mailbox['sender_email'] ?? $mailbox['smtp_username'] ?? '');
        $senderName = (string) ($mailbox['sender_name'] ?? '');
        $mail->setFrom($senderEmail, $senderName);
        $mail->addAddress($recipientEmail, $recipientName);

        $mail->isHTML(true);
        $mail->Subject = $subject;
        $mail->Body = $body;

        foreach ($attachments as $att) {
            $mail->addStringAttachment($att['content'], $att['filename'], PHPMailer::ENCODING_BASE64, $att['mime']);
        }

        $mail->send();
    }

    /**
     * @param list<array{filename: string, content: string, mime: string}> $attachments
     */
    private function logEmail(
        int $templateId,
        int $mailboxId,
        int $orderId,
        string $recipientEmail,
        string $recipientName,
        string $subject,
        string $bodyHtml,
        array $attachments,
        string $status,
        ?string $errorMessage,
        ?string $sentAt
    ): int {
        $attachmentsJson = [];
        foreach ($attachments as $att) {
            $attachmentsJson[] = [
                'name' => $att['filename'],
                'type' => $att['mime'],
            ];
        }

        $stmt = $this->pdo->prepare(
            'INSERT INTO email_logs (
                template_id, mailbox_id, order_id, recipient_email, recipient_name,
                subject, body_html, attachments_json, status, error_message, sent_at, created_at
            ) VALUES (
                :template_id, :mailbox_id, :order_id, :recipient_email, :recipient_name,
                :subject, :body_html, :attachments_json, :status, :error_message, :sent_at, NOW()
            )'
        );

        $stmt->execute([
            'template_id' => $templateId,
            'mailbox_id' => $mailboxId,
            'order_id' => $orderId,
            'recipient_email' => $recipientEmail,
            'recipient_name' => $recipientName,
            'subject' => $subject,
            'body_html' => $bodyHtml,
            'attachments_json' => json_encode($attachmentsJson, JSON_UNESCAPED_UNICODE),
            'status' => $status,
            'error_message' => $errorMessage,
            'sent_at' => $sentAt,
        ]);

        return (int) $this->pdo->lastInsertId();
    }
}
