<?php

namespace App\Livewire\Settings;

use App\Models\AuditEvent;
use App\Models\Setting;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Livewire\Component;
use Throwable;

/**
 * SMTP configuration card. The operator points the panel at an SMTP server so
 * password-reset mails (and future notifications) actually leave the box. Values
 * are stored as Setting rows and applied to runtime config by AppServiceProvider::boot().
 *
 * The SMTP password is the one secret here: it is stored ENCRYPTED at rest
 * (Crypt::encryptString, APP_KEY) — Setting::put stores plaintext, so we encrypt the
 * value ourselves — never rendered back to the browser, and never logged/echoed
 * (any error from the test-send is shown without the password).
 */
class Email extends Component
{
    public string $mail_host = '';

    public int $mail_port = 587;

    public string $mail_username = '';

    /** New password typed in the form. Empty = keep the stored one (never pre-filled). */
    public string $mail_password = '';

    public string $mail_encryption = 'tls';

    public string $mail_from_address = '';

    public string $mail_from_name = '';

    /** True once a password has been stored, so the field shows a "(set)" placeholder. */
    public bool $passwordSet = false;

    public function mount(): void
    {
        abort_unless(auth()->user()?->can('manage-panel'), 403);
        $this->mail_host = Setting::get('mail_host', '') ?? '';
        $this->mail_port = (int) (Setting::get('mail_port', '587') ?? 587);
        $this->mail_username = Setting::get('mail_username', '') ?? '';
        $this->mail_encryption = Setting::get('mail_encryption', 'tls') ?? 'tls';
        $this->mail_from_address = Setting::get('mail_from_address', '') ?? '';
        $this->mail_from_name = Setting::get('mail_from_name', '') ?? '';
        $this->passwordSet = (Setting::get('mail_password') ?? '') !== '';
    }

    protected function rules(): array
    {
        return [
            'mail_host' => ['required', 'string', 'max:255'],
            'mail_port' => ['required', 'integer', 'min:1', 'max:65535'],
            'mail_username' => ['nullable', 'string', 'max:255'],
            'mail_password' => ['nullable', 'string', 'max:255'],
            'mail_encryption' => ['required', 'in:none,tls,ssl'],
            'mail_from_address' => ['required', 'email', 'max:255'],
            'mail_from_name' => ['required', 'string', 'max:255'],
        ];
    }

    public function save(): void
    {
        abort_unless(auth()->user()?->can('manage-panel'), 403);
        $this->validate();

        Setting::put('mail_host', $this->mail_host);
        Setting::put('mail_port', (string) $this->mail_port);
        Setting::put('mail_username', $this->mail_username);
        Setting::put('mail_encryption', $this->mail_encryption);
        Setting::put('mail_from_address', $this->mail_from_address);
        Setting::put('mail_from_name', $this->mail_from_name);

        // Only overwrite the stored password when a new one was typed; otherwise keep
        // the existing (encrypted) value untouched. Store encrypted at rest.
        if ($this->mail_password !== '') {
            Setting::put('mail_password', Crypt::encryptString($this->mail_password));
            $this->passwordSet = true;
        }

        $this->reset('mail_password');

        AuditEvent::create([
            'user_id' => Auth::id(),
            'actor' => Auth::user()->name,
            'action' => 'mail.configure',
            'target' => $this->mail_host,
            'ip' => request()->ip(),
        ]);

        $this->dispatch('notify', message: __('mail.notify_saved'));
    }

    /**
     * Send a one-line test mail to the CURRENT user's own address, using a mailer built
     * from the saved settings. Never targets anyone else; never leaks the password — any
     * transport error is surfaced as a message with the secret masked out.
     */
    public function sendTest(): void
    {
        abort_unless(auth()->user()?->can('manage-panel'), 403);

        if (! $this->configured()) {
            $this->dispatch('notify', message: __('mail.notify_test_failed', ['error' => __('mail.not_configured')]));

            return;
        }

        // Throttle test-sends so the panel can't be turned into a spam/mail-bomb relay.
        // Per-user + auto-expiring: only ever limits the operator's own test button briefly.
        $key = 'mail-test:'.Auth::id();
        if (RateLimiter::tooManyAttempts($key, 3)) {
            $this->dispatch('notify', message: __('mail.notify_test_throttled', ['seconds' => RateLimiter::availableIn($key)]), level: 'error');

            return;
        }
        RateLimiter::hit($key, 600); // 3 test e-mails / 10 minutes per user

        $to = Auth::user()->email;

        try {
            $this->applyMailerConfig();

            Mail::raw(__('mail.test_body'), function ($message) use ($to) {
                $message->to($to)->subject(__('mail.test_subject'));
            });

            $this->dispatch('notify', message: __('mail.notify_test_ok', ['email' => $to]));
        } catch (Throwable $e) {
            $this->dispatch('notify', message: __('mail.notify_test_failed', [
                'error' => $this->maskSecret(Str::limit($e->getMessage(), 160)),
            ]));
        }
    }

    /** Host + from-address present — the minimum to actually send. */
    public function configured(): bool
    {
        return $this->mail_host !== '' && $this->mail_from_address !== '';
    }

    /**
     * Apply the saved SMTP settings to runtime config for the duration of this request,
     * so the test-send uses exactly what the operator configured (decrypting the stored
     * password). Mirrors AppServiceProvider's runtime override.
     */
    private function applyMailerConfig(): void
    {
        $password = '';
        $stored = Setting::get('mail_password');
        if ($stored !== null && $stored !== '') {
            try {
                $password = Crypt::decryptString($stored);
            } catch (Throwable) {
                $password = '';
            }
        }

        config([
            'mail.default' => 'smtp',
            'mail.mailers.smtp.host' => $this->mail_host,
            'mail.mailers.smtp.port' => $this->mail_port,
            'mail.mailers.smtp.username' => $this->mail_username ?: null,
            'mail.mailers.smtp.password' => $password ?: null,
            'mail.mailers.smtp.encryption' => $this->mail_encryption === 'none' ? null : $this->mail_encryption,
            'mail.from.address' => $this->mail_from_address,
            'mail.from.name' => $this->mail_from_name,
        ]);

        // Drop any mailer Laravel already resolved this request so the new config takes effect.
        Mail::purge('smtp');
    }

    /** Strip the stored SMTP password out of any string before it is shown/logged. */
    private function maskSecret(string $text): string
    {
        $stored = Setting::get('mail_password');
        if ($stored === null || $stored === '') {
            return $text;
        }

        try {
            $plain = Crypt::decryptString($stored);
        } catch (Throwable) {
            return $text;
        }

        return $plain !== '' ? str_replace($plain, '****', $text) : $text;
    }

    public function render()
    {
        return view('livewire.settings.email');
    }
}
