<?php

declare(strict_types=1);

trait MyronAuthAccountTrait
{
    public function ensureSeedData(): void
    {
        $existing = $this->pdo->query('SELECT role_key FROM roles')->fetchAll(PDO::FETCH_COLUMN);
        $existingKeys = array_fill_keys(array_map('strval', is_array($existing) ? $existing : []), true);

        $now = myronNow();
        $insert = $this->pdo->prepare(
            'INSERT INTO roles (id, role_key, name, description, created_at, updated_at)
             VALUES (:id, :role_key, :name, :description, :created_at, :updated_at)'
        );

        foreach (self::roleCatalog() as $roleKey => $role) {
            if (isset($existingKeys[$roleKey])) {
                continue;
            }

            $insert->execute([
                ':id' => myronGenerateId('role'),
                ':role_key' => $roleKey,
                ':name' => $role['name'],
                ':description' => $role['description'],
                ':created_at' => $now,
                ':updated_at' => $now,
            ]);
        }

        $statement = $this->pdo->prepare(
            'INSERT INTO registration_settings (id, allowed_domains_json, updated_at)
             SELECT 1, :allowed_domains_json, :updated_at
             WHERE NOT EXISTS (SELECT 1 FROM registration_settings WHERE id = 1)'
        );
        $statement->execute([
            ':allowed_domains_json' => json_encode([], JSON_THROW_ON_ERROR),
            ':updated_at' => $now,
        ]);
    }

    public function hasUsers(): bool
    {
        return (int) $this->pdo->query('SELECT COUNT(*) FROM users')->fetchColumn() > 0;
    }

    public function bootstrapFirstAdministrator(string $displayName, string $email, string $password): array
    {
        if ($this->hasUsers()) {
            throw new RuntimeException('The first user has already been created.');
        }

        $normalizedEmail = myronNormalizeEmail($email);
        if (!filter_var($normalizedEmail, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('A valid email address is required.');
        }

        $normalizedName = trim($displayName);
        if ($normalizedName === '') {
            throw new InvalidArgumentException('Display name is required.');
        }

        $this->assertPasswordPolicy($password);

        $role = $this->requireRoleByKey('administrator');
        $grantedScopesJson = self::encodeGrantedScopes(self::scopesForRoleKey('administrator'));
        $userId = myronGenerateId('user');
        $now = myronNow();

        $statement = $this->pdo->prepare(
            'INSERT INTO users (
                id, username, email, display_name, first_name, last_name, bio, avatar_url,
                facebook_url, x_url, pinterest_url, linkedin_url, youtube_url, instagram_url, tiktok_url,
                password_hash, role_id, granted_scopes_json, is_active, email_verified_at,
                created_at, updated_at, last_login_at
             ) VALUES (
                :id, :username, :email, :display_name, :first_name, :last_name, :bio, :avatar_url,
                :facebook_url, :x_url, :pinterest_url, :linkedin_url, :youtube_url, :instagram_url, :tiktok_url,
                :password_hash, :role_id, :granted_scopes_json, :is_active, :email_verified_at,
                :created_at, :updated_at, :last_login_at
             )'
        );
        $statement->execute([
            ':id' => $userId,
            ':username' => $this->generateUniqueUsername($normalizedEmail),
            ':email' => $normalizedEmail,
            ':display_name' => $normalizedName,
            ':first_name' => '',
            ':last_name' => '',
            ':bio' => '',
            ':avatar_url' => '',
            ':facebook_url' => '',
            ':x_url' => '',
            ':pinterest_url' => '',
            ':linkedin_url' => '',
            ':youtube_url' => '',
            ':instagram_url' => '',
            ':tiktok_url' => '',
            ':password_hash' => password_hash($password, PASSWORD_DEFAULT),
            ':role_id' => (string) $role['id'],
            ':granted_scopes_json' => $grantedScopesJson,
            ':is_active' => 1,
            ':email_verified_at' => $now,
            ':created_at' => $now,
            ':updated_at' => $now,
            ':last_login_at' => $now,
        ]);

        $user = $this->findUserById($userId);
        if ($user === null) {
            throw new RuntimeException('Failed to load the bootstrapped administrator account.');
        }

        return $user;
    }

    public function authenticate(string $email, string $password): ?array
    {
        $user = $this->findUserByEmail($email);
        if (
            !is_array($user)
            || (int) ($user['is_active'] ?? 0) !== 1
            || !password_verify($password, (string) $user['password_hash'])
        ) {
            return null;
        }

        return $user;
    }

    public function findUserById(string $userId): ?array
    {
        $statement = $this->pdo->prepare($this->baseUserSelect() . ' WHERE users.id = :user_id LIMIT 1');
        $statement->execute([':user_id' => $userId]);
        $user = $statement->fetch();

        return is_array($user) ? $this->decorateUserRecord($user) : null;
    }

    public function findUserByEmail(string $email): ?array
    {
        $normalizedEmail = myronNormalizeEmail($email);
        $statement = $this->pdo->prepare($this->baseUserSelect() . ' WHERE users.email = :email LIMIT 1');
        $statement->execute([':email' => $normalizedEmail]);
        $user = $statement->fetch();

        return is_array($user) ? $this->decorateUserRecord($user) : null;
    }

    public function updatePassword(string $userId, string $password): void
    {
        $this->assertPasswordPolicy($password);

        $statement = $this->pdo->prepare(
            'UPDATE users
             SET password_hash = :password_hash,
                 updated_at = :updated_at
             WHERE id = :user_id'
        );
        $statement->execute([
            ':password_hash' => password_hash($password, PASSWORD_DEFAULT),
            ':updated_at' => myronNow(),
            ':user_id' => $userId,
        ]);
    }

    public function markLogin(string $userId): void
    {
        $statement = $this->pdo->prepare(
            'UPDATE users
             SET last_login_at = :last_login_at,
                 updated_at = :updated_at
             WHERE id = :user_id'
        );
        $statement->execute([
            ':last_login_at' => myronNow(),
            ':updated_at' => myronNow(),
            ':user_id' => $userId,
        ]);
    }
}
