<?php

namespace App\Livewire\Concerns;

use App\Actions\Fortify\PasswordValidationRules;
use App\Actions\Fortify\UpdateUserPassword;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;

/**
 * Changing your own password, from wherever you are signed in.
 *
 * There was no way to do it at all: Fortify's updatePasswords feature was off,
 * so an account created with a generated password kept it until someone opened
 * a shell on the server. Shared by the console and the customer portal because
 * the rules are the same in both, and having two copies of a password rule is
 * how one of them ends up weaker.
 *
 * Goes through Fortify's own action rather than hashing here, so the password
 * rules stay in the single place that already defines them — on the `web`
 * guard, which is the only one UpdateUserPassword knows: it is typed to
 * `User` and pins its own `current_password` rule to `web`. An operator
 * validates against the SAME PasswordValidationRules and writes its own hash
 * here instead, rather than routing through an action written for a
 * different model on a different guard.
 */
trait ChangesOwnPassword
{
    use PasswordValidationRules;

    public string $currentPassword = '';

    public string $newPassword = '';

    public string $newPasswordConfirmation = '';

    /**
     * Which guard's account this component changes the password of.
     *
     * Overridden to 'operator' by the console's Settings page. A default of
     * 'web' keeps the portal working unchanged.
     */
    protected function passwordAccountGuard(): string
    {
        return 'web';
    }

    public function updateOwnPassword(): void
    {
        // The current password is asked for, and checked here as well as in the
        // action: an attacker on an unlocked machine should not be able to lock
        // the owner out of their own account with two keystrokes.
        $this->validate([
            'currentPassword' => 'required|string',
            'newPassword' => 'required|string',
            'newPasswordConfirmation' => 'required|string',
        ]);

        $guard = $this->passwordAccountGuard();
        $account = Auth::guard($guard)->user();

        if (! Hash::check($this->currentPassword, $account->password)) {
            $this->addError('currentPassword', __('admin_settings.password_wrong'));

            return;
        }

        if ($guard === 'web') {
            try {
                app(UpdateUserPassword::class)->update($account, [
                    'current_password' => $this->currentPassword,
                    'password' => $this->newPassword,
                    'password_confirmation' => $this->newPasswordConfirmation,
                ]);
            } catch (ValidationException $e) {
                foreach ($e->errors() as $field => $messages) {
                    $this->addError(match ($field) {
                        'current_password' => 'currentPassword',
                        'password' => 'newPassword',
                        default => $field,
                    }, $messages[0]);
                }

                return;
            }
        } else {
            try {
                Validator::make([
                    'password' => $this->newPassword,
                    'password_confirmation' => $this->newPasswordConfirmation,
                ], ['password' => $this->passwordRules()])->validate();
            } catch (ValidationException $e) {
                $this->addError('newPassword', $e->errors()['password'][0]);

                return;
            }

            $account->forceFill(['password' => Hash::make($this->newPassword)])->save();
        }

        $this->reset('currentPassword', 'newPassword', 'newPasswordConfirmation');
        $this->dispatch('notify', message: __('admin_settings.password_changed'));
    }
}
