<?php

declare(strict_types=1);

namespace Apexl\Io\Logger\Handlers;

use Apexl\Io\Factories\MailerFactory;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\BufferHandler;
use Monolog\Handler\MailHandler;
use Monolog\Logger;
use Throwable;

/**
 * Emails log records via the application mailer.
 *
 * Wrap this in a {@see BufferHandler} to get one email per
 * process rather than one per record.
 */
final class IoMailerHandler extends MailHandler
{
    /**
     * `%context.exception%` promotes a logged Throwable out of the context blob
     * and onto its own lines, where LineFormatter renders it with a readable
     * `[stacktrace]` block and any `[previous exception]` chain.
     */
    private const string FORMAT = "[%datetime%] %channel%.%level_name%: %message%\n"
        . "%context.exception%\n%context%%extra%\n";

    public function __construct(
        private readonly MailerFactory $mailerFactory,
        private readonly string $to,
        private readonly string $subject,
        int|string $level = Logger::WARNING,
        bool $bubble = true,
    ) {
        parent::__construct($level, $bubble);
    }

    protected function send(string $content, array $records): void
    {
        try {
            $mailer = $this->mailerFactory->make();
            $mailer->addAddress($this->to);
            $mailer->Subject = $this->subject;
            $mailer->Body = $content;
            $mailer->isHTML($this->isHtmlBody($content));

            $mailer->send();
        } catch (Throwable $throwable) {
            // Never let a failed notification take down the process it is reporting on.
            error_log(
                sprintf(
                    'Unable to email log records to %s: %s',
                    $this->to,
                    $throwable->getMessage()
                )
            );
        }
    }

    protected function getDefaultFormatter(): FormatterInterface
    {
        // Plain text rather than Monolog's default HtmlFormatter: a stack trace is
        // far more legible as text than JSON-encoded inside an HTML table.
        return new LineFormatter(self::FORMAT, ignoreEmptyContextAndExtra: true)
            ->includeStacktraces();
    }
}
