<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Ferienpass\CoreBundle\Notifier\Mime\NotificationEmail;
use Symfony\Component\Mime\Part\DataPart;

#[ORM\Entity]
class SentEmail extends SentMessage
{
    #[ORM\Column(type: 'json')]
    private array $emailData;

    /**
     * Recipient address, subject and — where the mail could be attributed to an
     * account — the recipient's name, lifted out of {@see $emailData} into plain
     * columns so the Postausgang can search them.
     *
     * Searching the JSON blob meant a `LIKE '%…%'` over every mail's full HTML body
     * on every keystroke, paying an off-page read per row. Matching narrow columns
     * still scans — a leading wildcard cannot seek an index — but it never touches
     * the blob, which is where the time actually went.
     *
     * Nullable: mail sent before these existed is backfilled by
     * {@see \Ferienpass\CoreBundle\Migration\SentEmailRecipientColumnsMigration},
     * and the getters read the JSON regardless, so display never depends on them.
     */
    #[ORM\Column(type: 'string', length: 255, nullable: true)]
    private ?string $toAddress = null;

    #[ORM\Column(type: 'string', length: 255, nullable: true)]
    private ?string $toName = null;

    #[ORM\Column(type: 'string', length: 255, nullable: true)]
    private ?string $subject = null;

    private function __construct(
        #[ORM\Column(type: 'string')]
        private string $emailMessageId
    ) {
        parent::__construct();
    }

    public static function fromNotificationEmail(NotificationEmail $email, string $messageId): static
    {
        $entity = new self($messageId);
        $entity->emailData = [
            'from' => ($email->getFrom()[0] ?? null)?->getAddress(),
            'to' => ($email->getTo()[0] ?? null)?->getAddress(),
            'replyTo' => ($email->getReplyTo()[0] ?? null)?->getAddress(),
            'subject' => $email->getSubject(),
            'type' => $email->getType(),
            'conversationItem' => $email->getConversationItem(),
            'text' => (string) $email->getTextBody(),
            'html' => (string) $email->getHtmlBody(),
            'attachments' => array_map(static fn (DataPart $attachment) => ['body' => null, 'filename' => $attachment->getFilename()], $email->getAttachments()),
        ];

        $entity->toAddress = self::clip($entity->emailData['to']);
        $entity->subject = self::clip($entity->emailData['subject']);

        return $entity;
    }

    /**
     * The recipient's name, when the mail could be attributed to an account. Set by
     * the log handler, which resolves the account from the recipient address.
     */
    public function setToName(?string $toName): void
    {
        $this->toName = self::clip($toName);
    }

    public function getToName(): ?string
    {
        return $this->toName;
    }

    public function getMessageId(): string
    {
        return $this->emailMessageId;
    }

    public function getFrom(): ?string
    {
        return $this->emailData['from'] ?? null;
    }

    public function getTo(): ?string
    {
        return $this->emailData['to'] ?? null;
    }

    public function getReplyTo(): ?string
    {
        return $this->emailData['replyTo'] ?? null;
    }

    public function getSubject(): ?string
    {
        return $this->emailData['subject'] ?? null;
    }

    /**
     * The notification type slug (e.g. "account_created"), matching the `type` route
     * parameter of the notification-template editor. Null for messages without a type.
     */
    public function getType(): ?string
    {
        return $this->emailData['type'] ?? null;
    }

    /**
     * The ConversationItem (rfc4122) this email belongs to, if it is a comment
     * notification — used to surface delivery/open status in the activity feed.
     */
    public function getConversationItem(): ?string
    {
        return $this->emailData['conversationItem'] ?? null;
    }

    public function getText(): ?string
    {
        return $this->emailData['text'] ?? null;
    }

    public function getHtml(): ?string
    {
        return $this->emailData['html'] ?? null;
    }

    public function getAttachments(): ?array
    {
        return $this->emailData['attachments'] ?? null;
    }

    private static function clip(?string $value): ?string
    {
        // Trimmed to null so a nameless account does not store " " (getName() joins
        // first and last name unconditionally) and shadow the address fallback.
        $value = trim((string) $value);

        return '' === $value ? null : mb_substr($value, 0, 255);
    }
}
