<?php

namespace App\Livewire;

use App\Livewire\Concerns\ConfirmsPassword;
use App\Models\Mailbox;
use App\Services\Mail\MailPurpose;
use App\Services\Secrets\SecretCipher;
use App\Support\Settings;
use LivewireUI\Modal\ModalComponent;

/**
 * Editing a mailbox, in a modal (R20).
 *
 * A modal is reachable WITHOUT the page's route middleware, so it authorises
 * itself and re-reads the record instead of trusting a property the browser
 * hydrated. save() re-authorises independently of mount() for the same reason
 * Secrets::guard() checks on every action rather than once: a Livewire action
 * is reachable by anyone who can post to /livewire/update, and a mount that
 * succeeded earlier in the session proves nothing about the request now.
 */
class EditMailbox extends ModalComponent
{
    use ConfirmsPassword;

    public string $uuid = '';

    public string $address = '';

    public string $displayName = '';

    public string $username = '';

    /** Always starts empty: a stored password never travels to the browser. */
    public string $password = '';

    public bool $noReply = false;

    public bool $active = true;

    /** Whether this mailbox logs in before it sends — Codex R15#4, P1b. */
    public bool $authenticates = true;

    protected function confirmationGuard(): string
    {
        return 'operator';
    }

    public function mount(string $uuid): void
    {
        $this->authorize('mail.manage');

        $box = Mailbox::query()->where('uuid', $uuid)->firstOrFail();

        $this->uuid = $box->uuid;
        $this->address = $box->address;
        $this->displayName = (string) $box->display_name;
        $this->username = (string) $box->username;
        $this->noReply = $box->no_reply;
        $this->active = $box->active;
        $this->authenticates = $box->authenticates;
    }

    public function save(): void
    {
        $this->authorize('mail.manage');

        $this->validate([
            'address' => ['required', 'email', 'max:255'],
            'displayName' => ['nullable', 'string', 'max:255'],
            'username' => ['nullable', 'string', 'max:255'],
            'password' => ['nullable', 'string', 'max:255'],
        ]);

        $box = Mailbox::query()->where('uuid', $this->uuid)->firstOrFail();

        // Deactivating the mailbox "system" currently points at is not just
        // this purpose losing its sender: MailboxResolver::active() filters an
        // inactive mailbox at BOTH the direct lookup AND the system fallback,
        // so every OTHER purpose that has no mailbox of its own falls through
        // to nothing too. savePurposes() already refuses to leave system
        // empty; this closes the other way to reach the same broken state.
        if (! $this->active && $box->key === (string) Settings::get(MailPurpose::settingKey(MailPurpose::SYSTEM))) {
            $this->addError('active', __('mail_settings.cannot_deactivate_system'));

            return;
        }

        // Setting a new SMTP password is one of the two actions the
        // mail.manage split still leaves able to intercept mail outright (the
        // other is Admin\Mail::saveServer()) — gated the same second way
        // Admin\Integrations' vault entries are: a capability decides who
        // may open this modal at all, a recent password decides whether
        // THIS session may point the
        // outgoing account at new credentials.
        if ($this->password !== '') {
            abort_unless($this->passwordRecentlyConfirmed(), 403);

            // A password can only be STORED where SECRETS_KEY exists to
            // encrypt it under — the same condition the page's own banner
            // already names. Checked here rather than left for
            // Mailbox::setPasswordAttribute() to throw through: an uncaught
            // RuntimeException would render Laravel's debug page, whose
            // request-payload inspector can show the very password just
            // typed. The page must say so, not crash on it.
            if (! app(SecretCipher::class)->isUsable()) {
                $this->addError('password', __('mail_settings.no_key'));

                return;
            }
        }

        $oldAddress = $box->address;
        $oldUsername = $box->username;
        $oldAuthenticates = $box->authenticates;

        $box->address = $this->address;
        $box->display_name = $this->displayName ?: null;
        $box->username = $this->username ?: null;
        $box->no_reply = $this->noReply;
        $box->active = $this->active;
        $box->authenticates = $this->authenticates;

        // Either one changes the identity smtpUsername() authenticates as:
        // username directly, or address as username's fallback when none is
        // set (including username being CLEARED back to that fallback).
        // authenticates itself is the fourth: whether that identity gets
        // sent at all. Leaving last_verified_at standing after any of the
        // three would keep showing a successful verification for a
        // connection nothing has actually tested since. Mailbox::
        // invalidateVerification() is the same clear Admin\Mail::saveServer()
        // uses for the server card, in its single-row shape — see that
        // method's own comment for why the two do NOT also share the "did
        // this actually change" comparison above this block.
        if ($box->address !== $oldAddress || $box->username !== $oldUsername || $box->authenticates !== $oldAuthenticates) {
            $box->invalidateVerification();
        }

        // Empty means "leave it alone", not "delete it" — otherwise every edit
        // of an address would silently drop the password.
        if ($this->password !== '') {
            $box->password = $this->password;
            $box->invalidateVerification();
        }

        $box->save();

        $this->password = '';
        $this->dispatch('notify', message: __('mail_settings.saved'));
        $this->dispatch('mailbox-saved');
        $this->closeModal();
    }

    public function render()
    {
        return view('livewire.edit-mailbox', [
            'passwordConfirmed' => $this->passwordRecentlyConfirmed(),
        ]);
    }
}
