<?php

declare(strict_types=1);

namespace App\Services;

use PHPMailer\PHPMailer\Exception as PhpMailerException;
use PHPMailer\PHPMailer\PHPMailer;

final class MailService extends BaseService
{
    public function __construct(
        private readonly SmtpConfigurationService $configuration =
            new SmtpConfigurationService()
    ) {
    }

    public function sendTest(?string $recipient = null): void
    {
        $settings = $this->configuration->getRuntimeConfiguration();

        $to = trim((string)($recipient ?: ($settings['smtp_test_email'] ?? '')));

        $this->send(
            recipient: $to,
            subject: 'Test SMTP — Darlink ERP Phoenix',
            htmlBody: '<p>La configuration SMTP de <strong>Darlink ERP</strong> fonctionne correctement.</p>',
            textBody: 'La configuration SMTP de Darlink ERP fonctionne correctement.'
        );
    }

    /**
     * @param array<int,array{name:string,content:string,mime?:string}> $attachments
     * @param array<int,string> $cc
     * @param array<int,string> $bcc
     */
    public function send(
        string $recipient,
        string $subject,
        string $htmlBody,
        ?string $textBody = null,
        array $attachments = [],
        array $cc = [],
        array $bcc = []
    ): void {
        if (!class_exists(PHPMailer::class)) {
            throw new \RuntimeException(
                'PHPMailer n’est pas installé. Exécutez composer install.'
            );
        }

        $settings = $this->configuration->getRuntimeConfiguration();

        if ((int)($settings['actif'] ?? 0) !== 1) {
            throw new \RuntimeException('La messagerie SMTP n’est pas activée.');
        }

        $recipient = trim($recipient);
        $subject = trim($subject);
        $htmlBody = trim($htmlBody);

        $this->assertEmail($recipient, 'L’adresse du destinataire');

        if ($subject === '') {
            throw new \InvalidArgumentException('L’objet de l’e-mail est obligatoire.');
        }

        if ($htmlBody === '') {
            throw new \InvalidArgumentException('Le message de l’e-mail est obligatoire.');
        }

        $mail = $this->createMailer($settings);
        $mail->addAddress($recipient);

        foreach ($cc as $email) {
            $email = trim((string)$email);
            if ($email === '') {
                continue;
            }
            $this->assertEmail($email, 'Une adresse en copie');
            $mail->addCC($email);
        }

        foreach ($bcc as $email) {
            $email = trim((string)$email);
            if ($email === '') {
                continue;
            }
            $this->assertEmail($email, 'Une adresse en copie cachée');
            $mail->addBCC($email);
        }

        $mail->Subject = $subject;
        $mail->Body = $htmlBody;
        $mail->AltBody = trim((string)($textBody ?? '')) !== ''
            ? (string)$textBody
            : $this->htmlToText($htmlBody);

        foreach ($attachments as $attachment) {
            $name = $this->safeAttachmentName((string)($attachment['name'] ?? 'document.pdf'));
            $content = (string)($attachment['content'] ?? '');
            $mime = trim((string)($attachment['mime'] ?? 'application/octet-stream'));

            if ($content === '') {
                throw new \InvalidArgumentException(
                    'La pièce jointe « ' . $name . ' » est vide.'
                );
            }

            $mail->addStringAttachment(
                $content,
                $name,
                PHPMailer::ENCODING_BASE64,
                $mime !== '' ? $mime : 'application/octet-stream'
            );
        }

        try {
            $mail->send();
        } catch (PhpMailerException $exception) {
            throw new \RuntimeException(
                'Échec de l’envoi SMTP : ' . $exception->getMessage(),
                0,
                $exception
            );
        }
    }

    /** @param array<string,mixed> $settings */
    private function createMailer(array $settings): PHPMailer
    {
        $mail = new PHPMailer(true);
        $mail->isSMTP();
        $mail->Host = (string)$settings['smtp_host'];
        $mail->Port = (int)$settings['smtp_port'];
        $mail->SMTPAuth = true;
        $mail->Username = (string)$settings['smtp_username'];
        $mail->Password = (string)$settings['smtp_password'];
        $mail->CharSet = 'UTF-8';
        $mail->isHTML(true);
        $mail->Timeout = 30;

        $security = (string)($settings['smtp_secure'] ?? 'tls');

        if ($security === 'tls') {
            $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
        } elseif ($security === 'ssl') {
            $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
        } else {
            $mail->SMTPSecure = '';
            $mail->SMTPAutoTLS = false;
        }

        $mail->setFrom(
            (string)$settings['smtp_from_email'],
            (string)$settings['smtp_from_name']
        );

        $replyTo = trim((string)($settings['smtp_reply_to'] ?? ''));
        if ($replyTo !== '') {
            $this->assertEmail($replyTo, 'L’adresse de réponse');
            $mail->addReplyTo($replyTo);
        }

        return $mail;
    }

    private function assertEmail(string $email, string $label): void
    {
        if ($email === '' || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
            throw new \InvalidArgumentException($label . ' n’est pas valide.');
        }
    }

    private function htmlToText(string $html): string
    {
        $text = preg_replace('/<br\s*\/?>/i', "\n", $html) ?? $html;
        $text = preg_replace('/<\/p>/i', "\n\n", $text) ?? $text;
        $text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $text = preg_replace("/\n{3,}/", "\n\n", $text) ?? $text;

        return trim($text);
    }

    private function safeAttachmentName(string $name): string
    {
        $name = basename(trim($name));
        $name = preg_replace('/[^A-Za-z0-9._-]+/', '_', $name) ?: 'document.pdf';

        return trim($name, '._') !== '' ? $name : 'document.pdf';
    }
}
