<?php

namespace App\Livewire\Settings;

use App\Models\AuditEvent;
use App\Models\User;
use App\Services\SessionService;
use App\Support\Confirm\ConfirmToken;
use App\Support\Confirm\InvalidConfirmToken;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\On;
use Livewire\Component;

/**
 * Multi-user account management. Every account is a full administrator — there are
 * no roles. Destructive actions (remove, force-logout) go through the R5 confirm
 * modal, which writes one AuditEvent and re-dispatches the apply event back here
 * (mirrors Settings\Security / Settings\Sessions).
 *
 * The current operator is always recorded as the actor on each audit row.
 */
class Users extends Component
{
    /** Open the CreateUser modal; it dispatches `usersChanged` on success. */
    public function create(): void
    {
        $this->dispatch('openModal', component: 'modals.create-user');
    }

    /**
     * Destructive (R5): open the confirm modal for removing an account. Guards
     * (self / last account) are enforced here AND again in the apply handler, so
     * a stale event can never slip past after the modal is open.
     */
    public function confirmRemove(int $userId): void
    {
        $user = User::find($userId);
        if (! $user || ! $this->canRemove($user)) {
            return;
        }

        $this->dispatch('openModal',
            component: 'modals.confirm-action',
            arguments: [
                'heading' => __('accounts.remove_heading'),
                'body' => __('accounts.remove_body'),
                'confirmLabel' => __('accounts.remove'),
                'danger' => true,
                'icon' => 'trash',
                // Defer the toast: the apply handler reports the real outcome.
                'notify' => '',
                'token' => ConfirmToken::issue(
                    'userRemoved',
                    ['userId' => $userId],
                    auditTarget: $user->email,
                ),
            ],
        );
    }

    #[On('userRemoved')]
    public function remove(string $confirmToken, SessionService $sessions): void
    {
        try {
            $payload = ConfirmToken::consume($confirmToken, 'userRemoved');
        } catch (InvalidConfirmToken) {
            return; // forged / replayed / direct-bypass attempt — no-op
        }
        $userId = $payload['params']['userId'];

        $self = Auth::id();

        // Atomic: lock the user rows + recount inside the transaction so two concurrent
        // deletes can't both pass the "more than one account" check and strand the
        // install with zero users (a lock-out). The last account is never removable.
        $email = DB::transaction(function () use ($userId, $self, $sessions): ?string {
            if (User::lockForUpdate()->count() <= 1) {
                return null;
            }

            $user = User::find($userId);
            if (! $user || $user->getKey() === $self) {
                return null;
            }

            $email = $user->email;

            // Kill every session + rotate the remember_token BEFORE deleting, so a
            // stolen remember-me cookie can never resurrect the account between the
            // session wipe and the row delete.
            $sessions->logoutUserEverywhere($user);
            $user->delete();

            return $email;
        });

        if ($email === null) {
            return;
        }

        $this->audit('user.delete', $email);
        $this->dispatch('notify', message: __('accounts.remove_notify'));
    }

    /**
     * Destructive (R5): open the confirm modal for signing another account out
     * of every device. Never offered for self (self uses Settings → Sessions).
     */
    public function confirmLogout(int $userId): void
    {
        $user = User::find($userId);
        if (! $user || $user->is(Auth::user())) {
            return;
        }

        $this->dispatch('openModal',
            component: 'modals.confirm-action',
            arguments: [
                'heading' => __('accounts.logout_heading'),
                'body' => __('accounts.logout_body'),
                'confirmLabel' => __('accounts.logout'),
                'danger' => true,
                'icon' => 'logout',
                'notify' => '',
                'token' => ConfirmToken::issue(
                    'userLoggedOut',
                    ['userId' => $userId],
                    auditTarget: $user->email,
                ),
            ],
        );
    }

    #[On('userLoggedOut')]
    public function logout(string $confirmToken, SessionService $sessions): void
    {
        try {
            $payload = ConfirmToken::consume($confirmToken, 'userLoggedOut');
        } catch (InvalidConfirmToken) {
            return; // forged / replayed / direct-bypass attempt — no-op
        }
        $userId = $payload['params']['userId'];

        $user = User::find($userId);
        if (! $user || $user->is(Auth::user())) {
            return;
        }

        $sessions->logoutUserEverywhere($user);

        $this->audit('user.logout', $user->email);
        $this->dispatch('notify', message: __('accounts.logout_notify'));
    }

    /** Reload the list after the CreateUser modal adds an account. */
    #[On('usersChanged')]
    public function refreshList(): void
    {
        // The list is re-fetched in render(); this handler just forces the round trip.
    }

    /**
     * A user may be removed unless it is the current operator or the last
     * remaining account (no lock-out). Mirrors the create-user "equal admins"
     * model — anyone can remove anyone else.
     */
    private function canRemove(User $user): bool
    {
        return ! $user->is(Auth::user()) && User::count() > 1;
    }

    private function audit(string $action, ?string $target): void
    {
        AuditEvent::create([
            'user_id' => Auth::id(),
            'actor' => Auth::user()?->name ?? 'system',
            'action' => $action,
            'target' => $target,
            'ip' => request()->ip(),
        ]);
    }

    public function render()
    {
        return view('livewire.settings.users', [
            'users' => User::query()->orderBy('name')->get(),
            'currentId' => Auth::id(),
        ]);
    }
}
