<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password;
use Illuminate\Validation\ValidationException;

/**
 * Creates the first master account, or an additional one.
 *
 * This exists instead of a seeder because a seeded login has a password that is
 * in version control. The command is the only way an account comes into being
 * before User Management exists, and it is part of the documented deploy
 * procedure.
 *
 * Non-interactive form, for scripted setup:
 *
 *   php artisan ams:create-master-user \
 *       --name="Priya Sharma" --email=priya@aumnitechworks.com --generate
 */
class CreateMasterUser extends Command
{
    /** @var string */
    protected $signature = 'ams:create-master-user
                            {--name= : Full name of the master user}
                            {--email= : Email address used to sign in}
                            {--password= : Password; omit to be prompted, or use --generate}
                            {--generate : Generate a random password and print it once}
                            {--force-change : Require the password to be changed at first sign-in}';

    /** @var string */
    protected $description = 'Create a master user account for Aumni AMS';

    public function handle(): int
    {
        $this->components->info('Creating a master user for Aumni AMS.');

        $name = $this->resolveName();
        $email = $this->resolveEmail();

        [$password, $wasGenerated] = $this->resolvePassword();

        if ($password === null) {
            return self::FAILURE;
        }

        try {
            $this->validate($name, $email, $password);
        } catch (ValidationException $exception) {
            foreach ($exception->validator->errors()->all() as $message) {
                $this->components->error($message);
            }

            return self::FAILURE;
        }

        $user = new User;
        $user->fill([
            'name' => $name,
            'email' => $email,
            'password' => $password,
            'is_master' => true,
            'is_active' => true,
            // A generated password is known to whoever ran this command, so the
            // account holder must replace it before doing anything else.
            'must_change_password' => $wasGenerated || (bool) $this->option('force-change'),
        ]);
        $user->password_changed_at = now();

        /*
         | Saving is enough to produce the audit entry: the Auditable trait
         | records the creation, with the attributes in new_values and `console`
         | as the URL. Logging it again here would put two rows in the trail for
         | one action, the second of them naming the new account as its own
         | creator — which is not true of anything.
         */
        $user->save();

        $this->newLine();
        $this->components->info(sprintf('Master user %s created.', $email));

        if ($wasGenerated) {
            $this->newLine();
            $this->components->warn('This password is shown once and is not recoverable. Record it now.');
            $this->components->twoColumnDetail('<fg=yellow>Temporary password</>', '<fg=yellow;options=bold>'.$password.'</>');
            $this->newLine();
        }

        if ($user->must_change_password) {
            $this->components->info('The account must choose a new password at first sign-in.');
        }

        return self::SUCCESS;
    }

    private function resolveName(): string
    {
        $name = (string) ($this->option('name') ?? '');

        while (trim($name) === '') {
            $name = (string) $this->ask('Full name');
        }

        return trim($name);
    }

    private function resolveEmail(): string
    {
        $email = (string) ($this->option('email') ?? '');

        while (trim($email) === '') {
            $email = (string) $this->ask('Email address');
        }

        return mb_strtolower(trim($email));
    }

    /**
     * @return array{0: string|null, 1: bool} the password, and whether it was generated
     */
    private function resolvePassword(): array
    {
        if ($this->option('generate')) {
            $length = (int) config('ams.security.generated_password_length', 14);

            return [Str::password($length, symbols: false), true];
        }

        $password = (string) ($this->option('password') ?? '');

        if ($password !== '') {
            return [$password, false];
        }

        $password = (string) $this->secret('Password');
        $confirmation = (string) $this->secret('Confirm password');

        if ($password !== $confirmation) {
            $this->components->error('The passwords do not match.');

            return [null, false];
        }

        return [$password, false];
    }

    /**
     * @throws ValidationException
     */
    private function validate(string $name, string $email, string $password): void
    {
        Validator::make(
            [
                'name' => $name,
                'email' => $email,
                'password' => $password,
            ],
            [
                'name' => ['required', 'string', 'max:120'],
                'email' => ['required', 'string', 'email', 'max:160', 'unique:users,email'],
                'password' => ['required', 'string', Password::default()],
            ],
        )->validate();
    }
}
