<?php

declare(strict_types=1);

namespace BotGuard\Services\Mail;

use BotGuard\Core\Crypto;
use BotGuard\Core\Logger;
use BotGuard\Core\View;
use BotGuard\Models\Setting;
use Throwable;

/**
 * High-level mail service.
 *
 * Resolves the effective SMTP configuration (admin settings override config
 * defaults), renders an email view, and dispatches it via SmtpMailer. Sending
 * is best-effort for transactional mail: failures are logged and surfaced via
 * the return value rather than throwing into request handling.
 */
final class Mailer
{
    private ?string $lastError = null;

    public function __construct(private Setting $settings, private Logger $logger)
    {
    }

    /**
     * True when a mail host is configured (DB or config).
     */
    public function isConfigured(): bool
    {
        return $this->config()['host'] !== '';
    }

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

    /**
     * Render an email view and send it. Returns false on failure (logged).
     *
     * @param array<string, mixed> $data
     */
    public function send(string $toEmail, string $subject, string $view, array $data = []): bool
    {
        $html = $this->render($view, array_merge($data, ['subject' => $subject]));

        return $this->sendHtml($toEmail, $subject, $html);
    }

    /**
     * Send pre-rendered HTML (used by the "send test email" action).
     */
    public function sendHtml(string $toEmail, string $subject, string $html): bool
    {
        $this->lastError = null;

        try {
            (new SmtpMailer($this->config()))->send($toEmail, $subject, $html);

            return true;
        } catch (Throwable $e) {
            $this->lastError = $e->getMessage();
            $this->logger->error('Email send failed', ['to' => $toEmail, 'error' => $e->getMessage()]);

            return false;
        }
    }

    /**
     * Resolve effective SMTP config: DB settings (smtp_*) over config('mail').
     *
     * @return array{host:string, port:int, encryption:string, username:string, password:string, from_address:string, from_name:string, timeout:int}
     */
    public function config(): array
    {
        $get = fn (string $key, mixed $default): mixed => $this->settings->value($key, $default);

        // The password is stored encrypted; decrypt the DB value, or fall back
        // to the (plaintext) config default when no DB value is present.
        $storedPw = $this->settings->value('smtp_password', null);
        $password = $storedPw !== null ? Crypto::decrypt((string) $storedPw) : (string) config('mail.password', '');

        return [
            'host'         => (string) $get('smtp_host', config('mail.host', '')),
            'port'         => (int) $get('smtp_port', config('mail.port', 587)),
            'encryption'   => (string) $get('smtp_encryption', config('mail.encryption', 'tls')),
            'username'     => (string) $get('smtp_username', config('mail.username', '')),
            'password'     => $password,
            'from_address' => (string) $get('smtp_from_address', config('mail.from_address', 'no-reply@example.com')),
            'from_name'    => (string) $get('smtp_from_name', config('mail.from_name', config('app.name', 'BotGuard'))),
            'timeout'      => (int) config('mail.timeout', 15),
        ];
    }

    /**
     * Render an email view with its own View instance (isolated from web state).
     *
     * @param array<string, mixed> $data
     */
    private function render(string $view, array $data): string
    {
        $renderer = new View(base_path('app/Views'));

        return $renderer->render('emails.' . $view, $data);
    }
}
