<?php

declare(strict_types=1);

namespace Database\Factories;

use App\Enums\UserRole;
use App\Enums\UserStatus;
use App\Models\Profile;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

/**
 * @extends Factory<User>
 */
class UserFactory extends Factory
{
    protected $model = User::class;

    /** Hashed once and reused: bcrypt per row makes a large suite crawl. */
    private static ?string $passwordHash = null;

    /** @return array<string, mixed> */
    public function definition(): array
    {
        return [
            'public_id' => (string) Str::ulid(),
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => self::$passwordHash ??= Hash::make('password-for-testing-1'),
            'role' => UserRole::User,
            'status' => UserStatus::Active,
            'locale' => 'en',
            'timezone' => 'UTC',
            'password_changed_at' => now(),
            'remember_token' => Str::random(10),

            /*
             * Nullable columns are set explicitly so a factory-built model
             * carries the same attribute set as one loaded from the database.
             * Strict mode treats a never-hydrated attribute as an error, so
             * without these, any code reading `two_factor_secret` or calling
             * `trashed()` blows up only when handed a created model -- a
             * failure that has nothing to do with the behaviour under test.
             */
            'phone' => null,
            'phone_verified_at' => null,
            'two_factor_secret' => null,
            'two_factor_recovery_codes' => null,
            'two_factor_confirmed_at' => null,
            'suspended_at' => null,
            'suspension_reason' => null,
            'suspended_by' => null,
            'last_login_at' => null,
            'last_login_ip' => null,
            'deleted_at' => null,
        ];
    }

    public function unverified(): static
    {
        return $this->state(fn (): array => ['email_verified_at' => null]);
    }

    public function suspended(): static
    {
        return $this->state(fn (): array => [
            'status' => UserStatus::Suspended,
            'suspended_at' => now(),
            'suspension_reason' => 'Testing suspension.',
        ]);
    }

    public function banned(): static
    {
        return $this->state(fn (): array => ['status' => UserStatus::Banned]);
    }

    public function support(): static
    {
        return $this->state(fn (): array => ['role' => UserRole::Support]);
    }

    public function admin(): static
    {
        return $this->state(fn (): array => ['role' => UserRole::Admin]);
    }

    public function superAdmin(): static
    {
        return $this->state(fn (): array => ['role' => UserRole::SuperAdmin]);
    }

    public function withTwoFactor(string $secret = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'): static
    {
        return $this->state(fn (): array => [
            'two_factor_secret' => $secret,
            'two_factor_recovery_codes' => [Hash::make('aaaaa-bbbbb')],
            'two_factor_confirmed_at' => now(),
        ]);
    }

    /**
     * Most tests need the profile that registration would have created, so it
     * is attached by default rather than in a separate state.
     */
    public function configure(): static
    {
        return $this->afterCreating(function (User $user): void {
            if (! $user->profile()->exists()) {
                $user->profile()->create([
                    'display_name' => $user->name,
                    'notification_preferences' => Profile::defaultNotificationPreferences(),
                ]);
            }
        });
    }
}
