<?php

namespace Tests\E2E\Services\Account;

use Appwrite\Tests\Retry;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
use Utopia\Database\DateTime;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\Datetime as DatetimeValidator;

use function sleep;

class AccountCustomClientTest extends Scope
{
    use AccountBase;
    use ProjectCustom;
    use SideClient;

    /**
     * Static cache for account data across tests
     */
    private static array $accountData = [];
    private static array $sessionData = [];
    private static array $updatedNameData = [];
    private static array $updatedPasswordData = [];
    private static array $updatedEmailData = [];
    private static array $updatedPrefsData = [];
    private static array $verificationData = [];
    private static array $verifiedData = [];
    private static array $recoveryData = [];
    private static array $phoneData = [];
    private static array $phoneSessionData = [];
    private static array $phonePasswordData = [];
    private static array $phoneUpdatedData = [];
    private static array $phoneVerificationData = [];
    private static array $magicUrlData = [];
    private static array $magicUrlSessionData = [];

    /**
     * Helper to set up an account with session
     */
    protected function setupAccountWithSession(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$sessionData[$cacheKey])) {
            return self::$sessionData[$cacheKey];
        }

        // First create an account
        $accountData = $this->setupAccount();

        $email = $accountData['email'];
        $password = $accountData['password'];

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $sessionId = $response['body']['$id'];
        $session = $response['cookies']['a_session_' . $projectId];

        self::$sessionData[$cacheKey] = array_merge($accountData, [
            'sessionId' => $sessionId,
            'session' => $session,
        ]);

        return self::$sessionData[$cacheKey];
    }

    /**
     * Helper to create a fresh account with session (bypasses cache).
     * Use this when you need a predictable session/log count for testing.
     */
    protected function createFreshAccountWithSession(): array
    {
        $projectId = $this->getProject()['$id'];

        // Use more entropy to avoid collisions in parallel test execution
        $email = uniqid('', true) . getmypid() . bin2hex(random_bytes(4)) . '@localhost.test';
        $password = 'password';
        $name = 'User Name';

        $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'password' => $password,
            'name' => $name,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $id = $response['body']['$id'];

        // Create session
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $sessionId = $response['body']['$id'];
        $session = $response['cookies']['a_session_' . $projectId];

        return [
            'id' => $id,
            'email' => $email,
            'password' => $password,
            'name' => $name,
            'sessionId' => $sessionId,
            'session' => $session,
        ];
    }

    /**
     * Helper to set up a basic account
     */
    protected function setupAccount(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$accountData[$cacheKey])) {
            return self::$accountData[$cacheKey];
        }

        $email = uniqid() . 'user@localhost.test';
        $password = 'password';
        $name = 'User Name';

        $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'password' => $password,
            'name' => $name,
        ]);

        $id = $response['body']['$id'];

        self::$accountData[$cacheKey] = [
            'id' => $id,
            'email' => $email,
            'password' => $password,
            'name' => $name,
        ];

        return self::$accountData[$cacheKey];
    }

    /**
     * Helper to set up account with updated name
     */
    protected function setupAccountWithUpdatedName(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$updatedNameData[$cacheKey])) {
            return self::$updatedNameData[$cacheKey];
        }

        $data = $this->setupAccountWithSession();
        $session = $data['session'];
        $newName = 'Lorem';

        $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
            'cookie' => 'a_session_' . $projectId . '=' . $session,
        ]), [
            'name' => $newName
        ]);

        self::$updatedNameData[$cacheKey] = array_merge($data, ['name' => $newName]);

        return self::$updatedNameData[$cacheKey];
    }

    /**
     * Helper to set up account with updated password
     */
    protected function setupAccountWithUpdatedPassword(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$updatedPasswordData[$cacheKey])) {
            return self::$updatedPasswordData[$cacheKey];
        }

        $data = $this->setupAccountWithUpdatedName();
        $session = $data['session'];
        $password = $data['password'];

        $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
            'cookie' => 'a_session_' . $projectId . '=' . $session,
        ]), [
            'password' => 'new-password',
            'oldPassword' => $password,
        ]);

        self::$updatedPasswordData[$cacheKey] = array_merge($data, ['password' => 'new-password']);

        return self::$updatedPasswordData[$cacheKey];
    }

    /**
     * Helper to set up account with updated email
     */
    protected function setupAccountWithUpdatedEmail(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$updatedEmailData[$cacheKey])) {
            return self::$updatedEmailData[$cacheKey];
        }

        $data = $this->setupAccountWithUpdatedPassword();
        $session = $data['session'];
        $newEmail = uniqid() . 'new@localhost.test';

        $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
            'cookie' => 'a_session_' . $projectId . '=' . $session,
        ]), [
            'email' => $newEmail,
            'password' => 'new-password',
        ]);

        self::$updatedEmailData[$cacheKey] = array_merge($data, ['email' => $newEmail]);

        return self::$updatedEmailData[$cacheKey];
    }

    /**
     * Helper to set up account with updated prefs
     */
    protected function setupAccountWithUpdatedPrefs(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$updatedPrefsData[$cacheKey])) {
            return self::$updatedPrefsData[$cacheKey];
        }

        $data = $this->setupAccountWithUpdatedEmail();
        $session = $data['session'];

        $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
            'cookie' => 'a_session_' . $projectId . '=' . $session,
        ]), [
            'prefs' => [
                'prefKey1' => 'prefValue1',
                'prefKey2' => 'prefValue2',
            ]
        ]);

        self::$updatedPrefsData[$cacheKey] = $data;

        return self::$updatedPrefsData[$cacheKey];
    }

    /**
     * Helper to set up account with verification created
     */
    protected function setupAccountWithVerification(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$verificationData[$cacheKey])) {
            return self::$verificationData[$cacheKey];
        }

        $data = $this->setupAccountWithUpdatedPrefs();
        $email = $data['email'];
        $session = $data['session'];

        $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
            'cookie' => 'a_session_' . $projectId . '=' . $session,
        ]), [
            'url' => 'http://localhost/verification',
        ]);

        $lastEmail = $this->getLastEmailByAddress($email);
        $tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);
        $verification = $tokens['secret'];

        self::$verificationData[$cacheKey] = array_merge($data, ['verification' => $verification]);

        return self::$verificationData[$cacheKey];
    }

    /**
     * Helper to set up account with verified email
     */
    protected function setupAccountWithVerifiedEmail(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$verifiedData[$cacheKey])) {
            return self::$verifiedData[$cacheKey];
        }

        $data = $this->setupAccountWithVerification();
        $id = $data['id'];
        $session = $data['session'];
        $verification = $data['verification'];

        $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
            'cookie' => 'a_session_' . $projectId . '=' . $session,
        ]), [
            'userId' => $id,
            'secret' => $verification,
        ]);

        self::$verifiedData[$cacheKey] = $data;

        return self::$verifiedData[$cacheKey];
    }

    /**
     * Helper to set up account with recovery token
     */
    protected function setupAccountWithRecovery(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$recoveryData[$cacheKey])) {
            return self::$recoveryData[$cacheKey];
        }

        $data = $this->setupAccountWithVerifiedEmail();
        $email = $data['email'];

        $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'email' => $email,
            'url' => 'http://localhost/recovery',
        ]);

        $lastEmail = $this->getLastEmailByAddress($email, function ($email) {
            $this->assertStringContainsString('Password Reset', $email['subject']);
        });
        $tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);
        $recovery = $tokens['secret'];

        self::$recoveryData[$cacheKey] = array_merge($data, ['recovery' => $recovery]);

        return self::$recoveryData[$cacheKey];
    }

    /**
     * Helper to set up phone account
     */
    protected function setupPhoneAccount(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$phoneData[$cacheKey])) {
            return self::$phoneData[$cacheKey];
        }

        // Ensure phone auth is enabled (may have been disabled by testPhoneVerification in parallel)
        $this->ensurePhoneAuthEnabled();

        // Use a truly unique phone number for parallel test safety
        // Combine microtime, PID, and random digits to avoid collisions across parallel processes
        $number = '+1' . substr(str_replace('.', '', microtime(true)) . getmypid() . random_int(100, 999), -9);

        $response = $this->client->call(Client::METHOD_POST, '/account/tokens/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'userId' => ID::unique(),
            'phone' => $number,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);

        $userId = $response['body']['userId'];

        $smsRequest = $this->getLastRequestForProject(
            $projectId,
            Scope::REQUEST_TYPE_SMS,
            [
                'header_X-Username' => 'username',
                'header_X-Key' => 'password',
                'method' => 'POST',
            ],
            probe: function (array $request) use ($number) {
                $this->assertEquals($number, $request['data']['to'] ?? null);
            }
        );

        $this->assertNotEmpty($smsRequest, 'SMS request not found for phone number: ' . $number);

        self::$phoneData[$cacheKey] = [
            'token' => $smsRequest['data']['message'],
            'id' => $userId,
            'number' => $number,
        ];

        return self::$phoneData[$cacheKey];
    }

    /**
     * Helper to set up phone session
     */
    protected function setupPhoneSession(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$phoneSessionData[$cacheKey])) {
            return self::$phoneSessionData[$cacheKey];
        }

        // Try up to 3 times with fresh phone accounts if session creation fails
        $maxRetries = 3;
        $lastError = '';

        for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
            // Force fresh phone account on retry
            if ($attempt > 0) {
                unset(self::$phoneData[$cacheKey]);
                \usleep(500000); // 500ms between retries
            }

            $data = $this->setupPhoneAccount();
            $id = $data['id'];
            // Extract OTP token - try the raw message first, then first word
            $rawMessage = $data['token'];
            $token = \trim($rawMessage);
            if (\str_contains($token, ' ')) {
                $token = \explode(' ', $token)[0];
            }

            $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
                'origin' => 'http://localhost',
                'content-type' => 'application/json',
                'x-appwrite-project' => $projectId,
            ]), [
                'userId' => $id,
                'secret' => $token,
            ]);

            if ($response['headers']['status-code'] === 201) {
                $session = $response['cookies']['a_session_' . $projectId];
                self::$phoneSessionData[$cacheKey] = array_merge($data, ['session' => $session]);
                return self::$phoneSessionData[$cacheKey];
            }

            $lastError = 'Attempt ' . ($attempt + 1) . ': Phone session creation failed (status ' . $response['headers']['status-code'] . '). Token: "' . $token . '", Raw message: "' . $rawMessage . '", UserId: ' . $id;
        }

        $this->fail($lastError);
    }

    /**
     * Helper to set up phone account converted to password
     */
    protected function setupPhoneConvertedToPassword(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$phonePasswordData[$cacheKey])) {
            return self::$phonePasswordData[$cacheKey];
        }

        $data = $this->setupPhoneSession();
        $session = $data['session'];
        $email = uniqid() . 'new@localhost.test';
        $password = 'new-password';

        $response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
            'cookie' => 'a_session_' . $projectId . '=' . $session,
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        // Re-login with email to get a fresh session after credential change
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);

        $session = $response['cookies']['a_session_' . $projectId];

        self::$phonePasswordData[$cacheKey] = array_merge($data, [
            'email' => $email,
            'password' => $password,
            'session' => $session,
        ]);

        return self::$phonePasswordData[$cacheKey];
    }

    /**
     * Helper to set up phone account with updated phone
     */
    protected function setupPhoneUpdated(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$phoneUpdatedData[$cacheKey])) {
            return self::$phoneUpdatedData[$cacheKey];
        }

        $data = $this->setupPhoneConvertedToPassword();
        $session = $data['session'];
        // Use a truly unique phone number to avoid target conflicts across parallel test runs
        $newPhone = '+456' . substr(str_replace('.', '', microtime(true)) . getmypid() . random_int(100, 999), -8);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
            'cookie' => 'a_session_' . $projectId . '=' . $session,
        ]), [
            'phone' => $newPhone,
            'password' => 'new-password'
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        self::$phoneUpdatedData[$cacheKey] = array_merge($data, ['phone' => $newPhone]);

        return self::$phoneUpdatedData[$cacheKey];
    }

    /**
     * Helper to set up phone verification
     */
    protected function setupPhoneVerification(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$phoneVerificationData[$cacheKey])) {
            return self::$phoneVerificationData[$cacheKey];
        }

        $data = $this->setupPhoneUpdated();
        $session = $data['session'];
        $phone = $data['phone'];

        $response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
            'cookie' => 'a_session_' . $projectId . '=' . $session,
        ]));

        $this->assertEquals(201, $response['headers']['status-code']);

        $tokenCreatedAt = $response['body']['$createdAt'];

        $smsRequest = $this->getLastRequestForProject(
            $projectId,
            Scope::REQUEST_TYPE_SMS,
            [
                'header_X-Username' => 'username',
                'header_X-Key' => 'password',
                'method' => 'POST',
            ],
            probe: function (array $request) use ($tokenCreatedAt, $phone) {
                if (!empty($phone)) {
                    $this->assertEquals($phone, $request['data']['to'] ?? null);
                }
                $tokenRecievedAt = $request['time'];
                $this->assertGreaterThan($tokenCreatedAt, $tokenRecievedAt);
            }
        );

        $this->assertNotEmpty($smsRequest, 'SMS request not found for phone verification');

        self::$phoneVerificationData[$cacheKey] = array_merge($data, [
            'token' => \substr($smsRequest['data']['message'], 0, 6)
        ]);

        return self::$phoneVerificationData[$cacheKey];
    }

    /**
     * Helper to set up magic URL account
     */
    protected function setupMagicUrl(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$magicUrlData[$cacheKey])) {
            return self::$magicUrlData[$cacheKey];
        }

        $email = \time() . 'user@appwrite.io';

        $response = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
        ]);

        $userId = $response['body']['userId'];

        $lastEmail = $this->getLastEmailByAddress($email);
        $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64);

        self::$magicUrlData[$cacheKey] = [
            'token' => $token,
            'id' => $userId,
            'email' => $email,
        ];

        return self::$magicUrlData[$cacheKey];
    }

    /**
     * Helper to set up magic URL session
     */
    protected function setupMagicUrlSession(): array
    {
        $projectId = $this->getProject()['$id'];
        $cacheKey = $projectId;

        if (!empty(self::$magicUrlSessionData[$cacheKey])) {
            return self::$magicUrlSessionData[$cacheKey];
        }

        $data = $this->setupMagicUrl();
        $id = $data['id'];
        $token = $data['token'];

        $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'userId' => $id,
            'secret' => $token,
        ]);

        $sessionId = $response['body']['$id'];
        $session = $response['cookies']['a_session_' . $projectId];

        self::$magicUrlSessionData[$cacheKey] = array_merge($data, [
            'sessionId' => $sessionId,
            'session' => $session,
        ]);

        return self::$magicUrlSessionData[$cacheKey];
    }

    /**
     * Helper to create an anonymous session (returns new session each time)
     */
    protected function createAnonymousSession(): string
    {
        $projectId = $this->getProject()['$id'];

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/anonymous', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]);

        return $response['cookies']['a_session_' . $projectId];
    }

    /**
     * Helper to delete any existing user with the given email.
     * Used to prevent parallel test conflicts when tests share
     * hardcoded emails (e.g. from mock OAuth providers).
     */
    protected function deleteUserByEmail(string $email): void
    {
        $projectId = $this->getProject()['$id'];
        $apiKey = $this->getProject()['apiKey'];

        $response = $this->client->call(Client::METHOD_GET, '/users', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
            'x-appwrite-key' => $apiKey,
        ], [
            'queries' => [
                Query::equal('email', [$email])->toString(),
            ],
        ]);

        if ($response['headers']['status-code'] === 200) {
            foreach ($response['body']['users'] ?? [] as $user) {
                $this->client->call(Client::METHOD_DELETE, '/users/' . $user['$id'], [
                    'content-type' => 'application/json',
                    'x-appwrite-project' => $projectId,
                    'x-appwrite-key' => $apiKey,
                ]);
            }
        }
    }

    /**
     * Helper to ensure phone auth is enabled for the project.
     * Needed because testPhoneVerification disables it and other
     * parallel tests may need it.
     */
    protected function ensurePhoneAuthEnabled(): void
    {
        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'x-appwrite-response-format' => '1.9.1',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'status' => true,
        ]);
    }

    public function testCreateAccountSession(): void
    {
        $data = $this->setupAccount();
        $email = $data['email'];
        $password = $data['password'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotFalse(\DateTime::createFromFormat('Y-m-d\TH:i:s.uP', $response['body']['expire']));

        $sessionId = $response['body']['$id'];
        $session = $response['cookies']['a_session_' . $this->getProject()['$id']];

        $accountResponse = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $accountResponse['headers']['status-code']);
        $this->assertEquals($email, $accountResponse['body']['email']);

        // apiKey is only available in custom client test
        $apiKey = $this->getProject()['apiKey'];
        if (!empty($apiKey)) {
            $userId = $response['body']['userId'];
            $response = $this->client->call(Client::METHOD_GET, '/users/' . $userId, array_merge([
                'origin' => 'http://localhost',
                'content-type' => 'application/json',
                'x-appwrite-project' => $this->getProject()['$id'],
                'x-appwrite-key' => $apiKey,
            ]));
            $this->assertEquals(200, $response['headers']['status-code']);
            $this->assertArrayHasKey('accessedAt', $response['body']);
            $this->assertNotEmpty($response['body']['accessedAt']);
        }

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertNotFalse(\DateTime::createFromFormat('Y-m-d\TH:i:s.uP', $response['body']['expire']));

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email . 'x',
            'password' => $password,
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password . 'x',
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => '',
            'password' => '',
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);
    }

    public function testGetAccount(): void
    {
        $data = $this->setupAccountWithSession();
        $email = $data['email'];
        $name = $data['name'];
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['email'], $email);
        $this->assertEquals($response['body']['name'], $name);
        $this->assertArrayHasKey('accessedAt', $response['body']);
        $this->assertNotEmpty($response['body']['accessedAt']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account', [
            'content-type' => 'application/json',
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session . 'xx',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testGetAccountPrefs(): void
    {
        $data = $this->setupAccountWithSession();
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_GET, '/account/prefs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertEmpty($response['body']);
        $this->assertCount(0, $response['body']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_GET, '/account/prefs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testGetAccountSessions(): void
    {
        // Use fresh account for predictable session count
        $data = $this->createFreshAccountWithSession();
        $session = $data['session'];
        $sessionId = $data['sessionId'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals(1, $response['body']['total']);
        $this->assertEquals($sessionId, $response['body']['sessions'][0]['$id']);
        $this->assertEmpty($response['body']['sessions'][0]['secret']);

        $this->assertEquals('Windows', $response['body']['sessions'][0]['osName']);
        $this->assertEquals('WIN', $response['body']['sessions'][0]['osCode']);
        $this->assertEquals('10', $response['body']['sessions'][0]['osVersion']);

        $this->assertEquals('browser', $response['body']['sessions'][0]['clientType']);
        $this->assertEquals('Chrome', $response['body']['sessions'][0]['clientName']);
        $this->assertEquals('CH', $response['body']['sessions'][0]['clientCode']);
        $this->assertEquals('70.0', $response['body']['sessions'][0]['clientVersion']);
        $this->assertEquals('Blink', $response['body']['sessions'][0]['clientEngine']);
        $this->assertEquals('desktop', $response['body']['sessions'][0]['deviceName']);
        $this->assertEquals('', $response['body']['sessions'][0]['deviceBrand']);
        $this->assertEquals('', $response['body']['sessions'][0]['deviceModel']);
        $this->assertEquals($response['body']['sessions'][0]['ip'], filter_var($response['body']['sessions'][0]['ip'], FILTER_VALIDATE_IP));

        $this->assertEquals('--', $response['body']['sessions'][0]['countryCode']);
        $this->assertEquals('Unknown', $response['body']['sessions'][0]['countryName']);

        $this->assertEquals(true, $response['body']['sessions'][0]['current']);

        $this->assertNotFalse(\DateTime::createFromFormat('Y-m-d\TH:i:s.uP', $response['body']['sessions'][0]['expire']));
        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testGetAccountLogs(): void
    {
        // Use fresh account for predictable log count
        $data = $this->createFreshAccountWithSession();
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertIsArray($response['body']['logs']);
        $this->assertNotEmpty($response['body']['logs']);
        // Fresh account: session.create is always logged. user.create audit may or may not
        // be present depending on async audit processing timing.
        $logCount = count($response['body']['logs']);
        $this->assertContains($logCount, [1, 2]);
        $this->assertIsNumeric($response['body']['total']);

        // Check session.create log (logs[0] - most recent)
        $this->assertEquals('Windows', $response['body']['logs'][0]['osName']);
        $this->assertEquals('WIN', $response['body']['logs'][0]['osCode']);
        $this->assertEquals('10', $response['body']['logs'][0]['osVersion']);

        $this->assertEquals('browser', $response['body']['logs'][0]['clientType']);
        $this->assertEquals('Chrome', $response['body']['logs'][0]['clientName']);
        $this->assertEquals('CH', $response['body']['logs'][0]['clientCode']);
        $this->assertEquals('70.0', $response['body']['logs'][0]['clientVersion']);
        $this->assertEquals('Blink', $response['body']['logs'][0]['clientEngine']);

        $this->assertEquals('desktop', $response['body']['logs'][0]['deviceName']);
        $this->assertEquals('', $response['body']['logs'][0]['deviceBrand']);
        $this->assertEquals('', $response['body']['logs'][0]['deviceModel']);
        $this->assertEquals(filter_var($response['body']['logs'][0]['ip'], FILTER_VALIDATE_IP), $response['body']['logs'][0]['ip']);

        $this->assertEquals('--', $response['body']['logs'][0]['countryCode']);
        $this->assertEquals('Unknown', $response['body']['logs'][0]['countryName']);

        if ($logCount === 2) {
            // Check user.create log (logs[1] - oldest)
            $this->assertEquals('user.create', $response['body']['logs'][1]['event']);
            $this->assertEquals(filter_var($response['body']['logs'][1]['ip'], FILTER_VALIDATE_IP), $response['body']['logs'][1]['ip']);
            $this->assertTrue((new DatetimeValidator())->isValid($response['body']['logs'][1]['time']));
        }

        $responseLimit = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'queries' => [
                Query::limit(1)->toString()
            ]
        ]);

        $this->assertEquals(200, $responseLimit['headers']['status-code']);
        $this->assertIsArray($responseLimit['body']['logs']);
        $this->assertNotEmpty($responseLimit['body']['logs']);
        $this->assertCount(1, $responseLimit['body']['logs']);
        $this->assertIsNumeric($responseLimit['body']['total']);

        $this->assertEquals($response['body']['logs'][0], $responseLimit['body']['logs'][0]);

        $responseOffset = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'queries' => [
                Query::offset(1)->toString()
            ]
        ]);

        $this->assertEquals($responseOffset['headers']['status-code'], 200);
        $this->assertIsArray($responseOffset['body']['logs']);
        // With offset(1), remaining logs = logCount - 1
        $this->assertCount($logCount - 1, $responseOffset['body']['logs']);
        $this->assertIsNumeric($responseOffset['body']['total']);

        if ($logCount === 2) {
            $this->assertEquals($response['body']['logs'][1], $responseOffset['body']['logs'][0]);
        }

        $responseLimitOffset = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'queries' => [
                Query::offset(1)->toString(),
                Query::limit(1)->toString()
            ]
        ]);

        $this->assertEquals(200, $responseLimitOffset['headers']['status-code']);
        $this->assertIsArray($responseLimitOffset['body']['logs']);
        // With offset(1)+limit(1), remaining logs = min(1, logCount - 1)
        $this->assertCount(min(1, $logCount - 1), $responseLimitOffset['body']['logs']);
        $this->assertIsNumeric($responseLimitOffset['body']['total']);

        if ($logCount === 2) {
            $this->assertEquals($response['body']['logs'][1], $responseLimitOffset['body']['logs'][0]);
        }

        /**
         * Test for total=false
         */
        $logsWithIncludeTotalFalse = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'total' => false
        ]);

        $this->assertEquals(200, $logsWithIncludeTotalFalse['headers']['status-code']);
        $this->assertIsArray($logsWithIncludeTotalFalse['body']);
        $this->assertIsArray($logsWithIncludeTotalFalse['body']['logs']);
        $this->assertIsInt($logsWithIncludeTotalFalse['body']['total']);
        $this->assertEquals(0, $logsWithIncludeTotalFalse['body']['total']);
        $this->assertGreaterThan(0, count($logsWithIncludeTotalFalse['body']['logs']));

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    // TODO Add tests for OAuth2 session creation

    public function testUpdateAccountName(): void
    {
        $data = $this->setupAccountWithSession();
        $email = $data['email'];
        $session = $data['session'];
        $newName = 'Lorem';

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'name' => $newName
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['email'], $email);
        $this->assertEquals($response['body']['name'], $newName);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), []);

        $this->assertEquals(400, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'name' => 'ocSRq1d3QphHivJyUmYY7WMnrxyjdk5YvVwcDqx2zS0coxESN8RmsQwLWw5Whnf0WbVohuFWTRAaoKgCOO0Y0M7LwgFnZmi8881Y72222222222222222222222222222'
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);
    }

    #[Retry(count: 1)]
    public function testUpdateAccountPassword(): void
    {
        $data = $this->setupAccountWithUpdatedName();
        $email = $data['email'];
        $password = $data['password'];
        $session = $data['session'];

        for ($i = 0; $i < 5; $i++) {
            $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
                'origin' => 'http://localhost',
                'content-type' => 'application/json',
                'x-appwrite-project' => $this->getProject()['$id'],
            ]), [
                'email' => $email,
                'password' => $password,
            ]);

            $this->assertEquals(201, $response['headers']['status-code']);
            usleep(500000);
        }

        $response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $allSessions = array_map(fn ($sessionDetails) => $sessionDetails['$id'], $response['body']['sessions']);

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'password' => 'new-password',
            'oldPassword' => $password,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['email'], $email);

        $currentSessionId = $data['sessionId'] ?? '';
        $response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals(1, $response['body']['total']);
        // checking the current session or not
        $this->assertEquals($currentSessionId, $response['body']['sessions'][0]['$id']);
        $this->assertTrue($response['body']['sessions'][0]['current']);

        // checking for all non active sessions are cleared
        foreach ($allSessions as $sessionId) {
            if ($currentSessionId === $sessionId) {
                $response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
                    'origin' => 'http://localhost',
                    'content-type' => 'application/json',
                    'x-appwrite-project' => $this->getProject()['$id'],
                    'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
                ]));

                $this->assertEquals(200, $response['headers']['status-code']);
            } else {
                $response = $this->client->call(Client::METHOD_GET, '/account/sessions/'.$sessionId, array_merge([
                    'origin' => 'http://localhost',
                    'content-type' => 'application/json',
                    'x-appwrite-project' => $this->getProject()['$id'],
                    'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
                ]));

                $this->assertEquals(404, $response['headers']['status-code']);
            }
        }

        $newPassword = 'new-password';
        // updating the invalidateSession to false to check sessions are not invalidated
        $this->updateProjectinvalidateSessionsProperty(false);
        for ($i = 0; $i < 5; $i++) {
            $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
                'origin' => 'http://localhost',
                'content-type' => 'application/json',
                'x-appwrite-project' => $this->getProject()['$id'],
            ]), [
                'email' => $email,
                'password' => $newPassword,
            ]);

            $this->assertEquals(201, $response['headers']['status-code']);
            usleep(500000);
        }

        $response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $allSessions = array_map(fn ($sessionDetails) => $sessionDetails['$id'], $response['body']['sessions']);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'password' => $newPassword,
            'oldPassword' => $newPassword,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        foreach ($allSessions as $sessionId) {
            $response = $this->client->call(Client::METHOD_GET, '/account/sessions/'.$sessionId, headers: array_merge([
                'origin' => 'http://localhost',
                'content-type' => 'application/json',
                'x-appwrite-project' => $this->getProject()['$id'],
                'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
            ]));

            $this->assertEquals(200, $response['headers']['status-code']);
        }

        // setting invalidateSession to true to check the sessions are cleared or not
        $this->updateProjectinvalidateSessionsProperty(true);
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'password' => $newPassword,
            'oldPassword' => $newPassword,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $allSessions = array_map(fn ($sessionDetails) => $sessionDetails['$id'], $response['body']['sessions']);

        foreach ($allSessions as $sessionId) {
            if ($currentSessionId !== $sessionId) {
                $response = $this->client->call(Client::METHOD_GET, '/account/sessions/'.$sessionId, array_merge([
                    'origin' => 'http://localhost',
                    'content-type' => 'application/json',
                    'x-appwrite-project' => $this->getProject()['$id'],
                    'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
                ]));

                $this->assertEquals(404, $response['headers']['status-code']);
            }
        }

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $newPassword,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);


        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), []);

        $this->assertEquals(400, $response['headers']['status-code']);

        /**
         * Existing user tries to update password by passing wrong old password -> SHOULD FAIL
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'password' => 'new-password',
            'oldPassword' => $password,
        ]);
        $this->assertEquals(401, $response['headers']['status-code']);

        /**
         * Existing user tries to update password without passing old password -> SHOULD FAIL
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'password' => 'new-password'
        ]);
        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testUpdateAccountEmail(): void
    {
        $data = $this->setupAccountWithUpdatedPassword();
        $newEmail = uniqid() . 'new@localhost.test';
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'email' => $newEmail,
            'password' => 'new-password',
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['email'], $newEmail);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), []);

        $this->assertEquals(400, $response['headers']['status-code']);

        // Test if we can create a new account with the old email

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? ''
        ]), [
            'userId' => ID::unique(),
            'email' => $data['email'],
            'password' => $data['password'],
            'name' => $data['name'],
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['email'], $data['email']);
        $this->assertEquals($response['body']['name'], $data['name']);
    }

    public function testUpdateAccountPrefs(): void
    {
        $data = $this->setupAccountWithUpdatedEmail();
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'prefs' => [
                'prefKey1' => 'prefValue1',
                'prefKey2' => 'prefValue2',
            ]
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertEquals('prefValue1', $response['body']['prefs']['prefKey1']);
        $this->assertEquals('prefValue2', $response['body']['prefs']['prefKey2']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'prefs' => '{}'
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);


        $response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'prefs' => '[]'
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'prefs' => '{"test": "value"}'
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);

        /**
         * Prefs size exceeded
         */
        $prefsObject = ["longValue" => str_repeat("🍰", 100000)];

        $response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'prefs' => $prefsObject
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);

        // Now let's test the same thing, but with normal symbol instead of multi-byte cake emoji
        $prefsObject = ["longValue" => str_repeat("-", 100000)];

        $response = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'prefs' => $prefsObject
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);
    }

    public function testCreateAccountVerification(): void
    {
        $data = $this->setupAccountWithUpdatedPrefs();
        $email = $data['email'];
        $name = $data['name'];
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,

        ]), [
            'url' => 'http://localhost/verification',
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));

        $lastEmail = $this->getLastEmailByAddress($email);

        $this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
        $this->assertEquals($name, $lastEmail['to'][0]['name']);
        $this->assertEquals('Account Verification for ' . $this->getProject()['name'], $lastEmail['subject']);
        $this->assertStringContainsStringIgnoringCase('Verify your email to activate your ' . $this->getProject()['name'] . ' account.', $lastEmail['text']);

        $tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);
        $verification = $tokens['secret'];
        $expectedExpire = DateTime::formatTz($response['body']['expire']);
        $this->assertEquals($expectedExpire, $tokens['expire']);

        // Secret check
        $this->assertArrayHasKey('secret', $tokens);
        $this->assertNotEmpty($tokens['secret']);

        // User ID check
        $this->assertArrayHasKey('userId', $tokens);
        $this->assertNotEmpty($tokens['userId']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'url' => 'localhost/verification',
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'url' => 'http://remotehost/verification',
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);
    }

    public function testUpdateAccountVerification(): void
    {
        $data = $this->setupAccountWithVerification();
        $id = $data['id'];
        $session = $data['session'];
        $verification = $data['verification'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'userId' => $id,
            'secret' => $verification,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'userId' => ID::custom('ewewe'),
            'secret' => $verification,
        ]);

        $this->assertEquals(404, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'userId' => $id,
            'secret' => 'sdasdasdasd',
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testDeleteAccountSession(): void
    {
        $data = $this->setupAccountWithVerifiedEmail();
        $email = $data['email'];
        $password = $data['password'];
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $sessionNewId = $response['body']['$id'];
        $sessionNew = $response['cookies']['a_session_' . $this->getProject()['$id']];

        $this->assertEquals(201, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_DELETE, '/account/sessions/' . $sessionNewId, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
        ]));

        $this->assertEquals(204, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_GET, '/account', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testDeleteAccountSessionCurrent(): void
    {
        $data = $this->setupAccountWithVerifiedEmail();
        $email = $data['email'];
        $password = $data['password'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $sessionNew = $response['cookies']['a_session_' . $this->getProject()['$id']];

        $this->assertEquals(201, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
            'x-appwrite-project' => $this->getProject()['$id'],
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_DELETE, '/account/sessions/current', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
            'x-appwrite-project' => $this->getProject()['$id'],
        ]);

        $this->assertEquals(204, $response['headers']['status-code']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_GET, '/account', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $sessionNew,
            'x-appwrite-project' => $this->getProject()['$id'],
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testDeleteAccountSessions(): void
    {
        $data = $this->setupAccountWithVerifiedEmail();
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_DELETE, '/account/sessions', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(204, $response['headers']['status-code']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);

    }

    public function testCreateAccountRecovery(): void
    {
        $data = $this->setupAccountWithVerifiedEmail();
        $email = $data['email'];
        $name = $data['name'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'url' => 'http://localhost/recovery',
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));

        $lastEmail = $this->getLastEmailByAddress($email, function ($email) {
            $this->assertStringContainsString('Password Reset', $email['subject']);
        });

        $this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
        $this->assertEquals($name, $lastEmail['to'][0]['name']);
        $this->assertEquals('Password Reset for ' . $this->getProject()['name'], $lastEmail['subject']);
        $this->assertStringContainsStringIgnoringCase('Reset your ' . $this->getProject()['name'] . ' password using the link.', $lastEmail['text']);


        $tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);

        // Secret check
        $this->assertArrayHasKey('secret', $tokens);
        $this->assertNotEmpty($tokens['secret']);
        $this->assertNotFalse($response['body']['secret']);

        // User ID check
        $this->assertArrayHasKey('userId', $tokens);
        $this->assertNotEmpty($tokens['userId']);
        $this->assertNotFalse($response['body']['userId']);

        // Expire check
        $this->assertArrayHasKey('expire', $tokens);
        $this->assertNotEmpty($tokens['expire']);
        $this->assertEquals(
            DateTime::formatTz($response['body']['expire']),
            $tokens['expire']
        );

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'url' => 'localhost/recovery',
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'url' => 'http://remotehost/recovery',
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => 'not-found@localhost.test',
            'url' => 'http://localhost/recovery',
        ]);

        $this->assertEquals(404, $response['headers']['status-code']);
    }

    #[Retry(count: 1)]
    public function testUpdateAccountRecovery(): void
    {
        $data = $this->setupAccountWithRecovery();
        $id = $data['id'];
        $recovery = $data['recovery'];
        $newPassword = 'test-recovery';

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => $id,
            'secret' => $recovery,
            'password' => $newPassword,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::custom('ewewe'),
            'secret' => $recovery,
            'password' => $newPassword,
        ]);

        $this->assertEquals(404, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => $id,
            'secret' => 'sdasdasdasd',
            'password' => $newPassword,
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testSessionAlert(): void
    {
        $email = uniqid() . 'session-alert@appwrite.io';
        $password = 'password123';
        $name = 'Session Alert Tester';

        // Enable session alerts
        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/session-alerts', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'x-appwrite-response-format' => '1.9.1',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'alerts' => true,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        // Create a new account
        $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? ''
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'password' => $password,
            'name' => $name,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);

        // Create first session for the new account
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);

        // Create second session for the new account
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
        ]), [
            'email' => $email,
            'password' => $password,
        ]);


        // Check the alert email
        $lastEmail = $this->getLastEmailByAddress($email);

        $this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
        $this->assertStringContainsString('Security alert: new session', $lastEmail['subject']);
        $this->assertStringContainsString($response['body']['ip'], $lastEmail['text']); // IP Address
        $this->assertStringContainsString('Unknown', $lastEmail['text']); // Country
        $this->assertStringContainsString($response['body']['clientName'], $lastEmail['text']); // Client name
        $this->assertStringNotContainsStringIgnoringCase('Appwrite logo', $lastEmail['html']);

        // Verify no alert sent in OTP login
        $response = $this->client->call(Client::METHOD_POST, '/account/tokens/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'email' => 'otpuser3@appwrite.io'
        ]);

        $this->assertEquals($response['headers']['status-code'], 201);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertNotEmpty($response['body']['$createdAt']);
        $this->assertNotEmpty($response['body']['userId']);
        $this->assertNotEmpty($response['body']['expire']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertEmpty($response['body']['phrase']);
        $this->assertStringContainsStringIgnoringCase('New login detected on '. $this->getProject()['name'], $lastEmail['text']);

        $userId = $response['body']['userId'];

        $lastEmail = $this->getLastEmailByAddress('otpuser3@appwrite.io');

        $this->assertNotEmpty($lastEmail, 'Email not found for address: otpuser3@appwrite.io');
        $this->assertEquals('OTP for ' . $this->getProject()['name'] . ' Login', $lastEmail['subject']);

        // Find 6 concurrent digits in email text - OTP
        preg_match_all("/\b\d{6}\b/", $lastEmail['text'], $matches);
        $code = $matches[0][0] ?? '';

        $this->assertNotEmpty($code);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => $userId,
            'secret' => $code
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertEquals($userId, $response['body']['userId']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertNotEmpty($response['body']['expire']);
        $this->assertEmpty($response['body']['secret']);

        $lastEmailId = $lastEmail['id'];
        $lastEmail = $this->getLastEmailByAddress('otpuser3@appwrite.io');
        $this->assertEquals($lastEmailId, $lastEmail['id']);
    }

    public function testCreateOAuth2AccountSession(): void
    {
        // Just ensure we have a session set up
        $this->setupAccountWithSession();

        $provider = 'mock';
        $appId = '1';
        $secret = '123456';

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'provider' => $provider,
            'appId' => $appId,
            'secret' => $secret,
            'enabled' => true,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
            'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
        ], followRedirects: false);

        $this->assertEquals(301, $response['headers']['status-code']);
        $this->assertStringStartsWith('http://localhost/v1/mock/tests/general/oauth2', $response['headers']['location']);

        $oauthClient = new Client();
        $oauthClient->setEndpoint('');
        $response = $oauthClient->call(Client::METHOD_GET, $response['headers']['location'], followRedirects: false);

        $this->assertEquals(301, $response['headers']['status-code']);
        $this->assertStringStartsWith('http://appwrite:/v1/account/sessions/oauth2/callback/mock/' . $this->getProject()['$id'] . '?code=', $response['headers']['location']);

        $response = $oauthClient->call(Client::METHOD_GET, $response['headers']['location'], followRedirects: false);

        $this->assertEquals(301, $response['headers']['status-code']);
        $this->assertStringStartsWith('http://appwrite:/v1/account/sessions/oauth2/mock/redirect?code=', $response['headers']['location']);

        $response = $oauthClient->call(Client::METHOD_GET, $response['headers']['location'], followRedirects: false);

        $this->assertEquals(301, $response['headers']['status-code']);

        $this->assertArrayHasKey('a_session_' . $this->getProject()['$id'] . '_legacy', $response['cookies']);
        $this->assertArrayHasKey('a_session_' . $this->getProject()['$id'], $response['cookies']);

        $oauthUserCookie = $response['cookies']['a_session_' . $this->getProject()['$id']];
        $this->assertNotEmpty($oauthUserCookie);

        $response = $oauthClient->call(Client::METHOD_GET, $response['headers']['location'], followRedirects: false);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals('success', $response['body']['result']);

        // Ensure user is authenticated
        $response = $this->client->call(Client::METHOD_GET, '/account', [
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $oauthUserCookie,
        ]);
        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals('useroauth@localhost.test', $response['body']['email']);

        $oauthUserId = $response['body']['$id'];
        $this->assertNotEmpty($oauthUserId);

        // Ensure session looks as expected
        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', [
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $oauthUserCookie,
        ]);
        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals($oauthUserId, $response['body']['userId']);
        $this->assertEquals('mock', $response['body']['provider']);

        // Same sign-in again, but this time with oauth2 token flow
        $response = $this->client->call(Client::METHOD_GET, '/account/tokens/oauth2/' . $provider, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
            'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
        ], followRedirects: false);

        $this->assertEquals(301, $response['headers']['status-code']);
        $this->assertStringStartsWith('http://localhost/v1/mock/tests/general/oauth2', $response['headers']['location']);

        $oauthClient = new Client();
        $oauthClient->setEndpoint('');
        $response = $oauthClient->call(Client::METHOD_GET, $response['headers']['location'], followRedirects: false);

        $this->assertEquals(301, $response['headers']['status-code']);
        $this->assertStringStartsWith('http://appwrite:/v1/account/sessions/oauth2/callback/mock/' . $this->getProject()['$id'] . '?code=', $response['headers']['location']);

        $response = $oauthClient->call(Client::METHOD_GET, $response['headers']['location'], followRedirects: false);

        $this->assertEquals(301, $response['headers']['status-code']);
        $this->assertStringStartsWith('http://appwrite:/v1/account/sessions/oauth2/mock/redirect?code=', $response['headers']['location']);

        $response = $oauthClient->call(Client::METHOD_GET, $response['headers']['location'], followRedirects: false);

        $this->assertEquals(301, $response['headers']['status-code']);
        $this->assertStringStartsWith('http://localhost/v1/mock/tests/general/oauth2/success?secret=', $response['headers']['location']);

        $oauthParamsString = \parse_url($response['headers']['location'], PHP_URL_QUERY);
        $oauthParams = [];
        \parse_str($oauthParamsString, $oauthParams);

        $this->assertNotEmpty($oauthParams['secret']);
        $this->assertNotEmpty($oauthParams['userId']);

        $response = $oauthClient->call(Client::METHOD_GET, $response['headers']['location'], followRedirects: false);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals('success', $response['body']['result']);

        // Claim session
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], [
            'userId' => $oauthParams['userId'],
            'secret' => $oauthParams['secret'],
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertEquals('mock', $response['body']['provider']);

        $this->assertArrayHasKey('a_session_' . $this->getProject()['$id'] . '_legacy', $response['cookies']);
        $this->assertArrayHasKey('a_session_' . $this->getProject()['$id'], $response['cookies']);

        $oauthUserCookie = $response['cookies']['a_session_' . $this->getProject()['$id']];
        $this->assertNotEmpty($oauthUserCookie);

        $response = $this->client->call(Client::METHOD_GET, '/account', [
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $oauthUserCookie,
        ]);
        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals('useroauth@localhost.test', $response['body']['email']);

        $oauthUserId = $response['body']['$id'];
        $this->assertNotEmpty($oauthUserId);

        // Ensure session looks as expected
        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', [
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $oauthUserCookie,
        ]);
        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals($oauthUserId, $response['body']['userId']);
        $this->assertEquals('mock', $response['body']['provider']);

        /**
         * Test for Failure when disabled
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'provider' => $provider,
            'appId' => $appId,
            'secret' => $secret,
            'enabled' => false,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
            'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
        ]);

        $this->assertEquals(412, $response['headers']['status-code']);
    }

    public function testCreateOidcOAuth2Token(): void
    {
        $provider = 'oidc';
        $appId = '1';

        // Valid well-known configuration
        $secret = '{
            "wellKnownEndpoint": "https://accounts.google.com/.well-known/openid-configuration",
            "authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
            "tokenEndpoint": "https://oauth2.googleapis.com/token",
            "userinfoEndpoint": "https://openidconnect.googleapis.com/v1/userinfo"
        }';

        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'provider' => $provider,
            'appId' => $appId,
            'secret' => $secret,
            'enabled' => true,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account/tokens/oauth2/' . $provider, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'provider' => $provider,
            'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
            'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
        ], true, false);

        $this->assertEquals(301, $response['headers']['status-code']);

        // Invalid well-known configuration
        $secret = '{}';

        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'provider' => $provider,
            'appId' => $appId,
            'secret' => $secret,
            'enabled' => true,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account/tokens/oauth2/' . $provider, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'provider' => $provider,
            'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
            'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
        ]);

        $this->assertEquals(500, $response['headers']['status-code']);

        // Clean up - disable the OIDC provider to avoid polluting other parallel tests
        $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'provider' => $provider,
            'enabled' => false,
        ]);
    }

    public function testBlockedAccount(): void
    {
        $email = uniqid() . 'user@localhost.test';
        $password = 'password';
        $name = 'User Name (blocked)';

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'password' => $password,
            'name' => $name,
        ]);

        $id = $response['body']['$id'];

        $this->assertEquals(201, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);

        $sessionId = $response['body']['$id'];
        $session = $response['cookies']['a_session_' . $this->getProject()['$id']];

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PATCH, '/users/' . $id . '/status', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ], [
            'status' => false,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(403, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(403, $response['headers']['status-code']);
    }


    public function testSelfBlockedAccount(): void
    {
        $email = uniqid() . 'user55@localhost.test';
        $password = 'password';
        $name = 'User Name (self blocked)';

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'password' => $password,
            'name' => $name,
        ]);

        $id = $response['body']['$id'];

        $this->assertEquals(201, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);

        $session = $response['cookies']['a_session_' . $this->getProject()['$id']];

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/status', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ], [
            'status' => false,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertStringContainsString('a_session_' . $this->getProject()['$id'] . '=deleted', $response['headers']['set-cookie']);
        $this->assertEquals('[]', $response['headers']['x-fallback-cookies']);

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(403, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(403, $response['headers']['status-code']);
    }

    public function testCreateJWT(): void
    {
        $email = uniqid() . 'user@localhost.test';
        $password = 'password';
        $name = 'User Name (JWT)';

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'password' => $password,
            'name' => $name,
        ]);

        $id = $response['body']['$id'];

        $this->assertEquals(201, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);

        $sessionId = $response['body']['$id'];
        $session = $response['cookies']['a_session_' . $this->getProject()['$id']];

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/jwt', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertEquals(119, $response['headers']['x-ratelimit-remaining']);
        $this->assertNotEmpty($response['body']['jwt']);
        $this->assertIsString($response['body']['jwt']);

        $jwt = $response['body']['jwt'];

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-jwt' => 'wrong-token',
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-jwt' => $jwt,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_DELETE, '/account/sessions/' . $sessionId, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(204, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-jwt' => $jwt,
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);

        // Test JWT with custom duration
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);

        $session = $response['cookies']['a_session_' . $this->getProject()['$id']];

        $response = $this->client->call(Client::METHOD_POST, '/account/jwt', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'duration' => 5
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']['jwt']);

        $jwt = $response['body']['jwt'];

        // Ensure JWT works before expiration
        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-jwt' => $jwt,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);

        // Wait for JWT to expire
        \sleep(6);

        // Ensure JWT no longer works after expiration
        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-jwt' => $jwt,
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testCreateAnonymousAccount(): void
    {
        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/anonymous', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertEmpty($response['body']['secret']);

        $session = $response['cookies']['a_session_' . $this->getProject()['$id']];

        \usleep(1000 * 30); // wait for 30ms to let the shutdown update accessedAt

        $apiKey = $this->getProject()['apiKey'];
        $userId = $response['body']['userId'];
        $response = $this->client->call(Client::METHOD_GET, '/users/' . $userId, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $apiKey,
        ]));
        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertArrayHasKey('accessedAt', $response['body']);

        $this->assertNotEmpty($response['body']['accessedAt']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/anonymous', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testCreateAnonymousAccountVerification(): void
    {
        $session = $this->createAnonymousSession();
        $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'url' => 'http://localhost/verification',
        ]);

        $this->assertEquals(400, $response['body']['code']);
        $this->assertEquals('user_email_not_found', $response['body']['type']);
    }

    public function testUpdateAnonymousAccountPassword(): void
    {
        $session = $this->createAnonymousSession();
        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'oldPassword' => '',
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);
    }

    public function testUpdateAnonymousAccountEmail(): void
    {
        $session = $this->createAnonymousSession();
        $email = uniqid() . 'new@localhost.test';

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'email' => $email,
            'password' => '',
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);
    }

    public function testConvertAnonymousAccount(): void
    {
        $session = $this->createAnonymousSession();
        $email = uniqid() . 'new@localhost.test';
        $password = 'new-password';

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'password' => $password
        ]);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(409, $response['headers']['status-code']);

        /**
         * Test for SUCCESS
         */
        $email = uniqid() . 'new@localhost.test';

        $response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['email'], $email);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'url' => 'http://localhost'
        ]);


        $this->assertEquals(201, $response['headers']['status-code']);
    }

    public function testConvertAnonymousAccountOAuth2(): void
    {
        // Clean up any existing user with the mock OAuth email to prevent
        // conflicts with parallel tests that also use the mock provider
        $this->deleteUserByEmail('useroauth@localhost.test');

        $session = $this->createAnonymousSession();
        $provider = 'mock';
        $appId = '1';
        $secret = '123456';

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);

        $userId = $response['body']['$id'] ?? '';

        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'provider' => $provider,
            'appId' => $appId,
            'secret' => $secret,
            'enabled' => true,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        // Delete any user with the mock OAuth email right before the OAuth call
        // to minimize the race window with parallel tests
        $this->deleteUserByEmail('useroauth@localhost.test');

        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
            'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        $sessionCookieKey = 'a_session_' . $this->getProject()['$id'];
        $this->assertArrayHasKey(
            $sessionCookieKey,
            $response['cookies'],
            "Failed asserting that session cookie '$sessionCookieKey' is set. Cookies: " . json_encode($response['cookies'])
        );
        $session = $response['cookies'][$sessionCookieKey];

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals($response['body']['$id'], $userId);
        $this->assertEquals('User Name', $response['body']['name']);
        $this->assertEquals('useroauth@localhost.test', $response['body']['email']);

        // Since we only support one oauth user, let's also check updateSession here

        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertEquals('123456', $response['body']['providerAccessToken']);
        $this->assertEquals('tuvwxyz', $response['body']['providerRefreshToken']);
        $this->assertGreaterThan(DateTime::addSeconds(new \DateTime(), 14400 - 5), $response['body']['providerAccessTokenExpiry']); // 5 seconds allowed networking delay

        $initialExpiry = $response['body']['providerAccessTokenExpiry'];

        sleep(3);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/sessions/current', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals('123456', $response['body']['providerAccessToken']);
        $this->assertEquals('tuvwxyz', $response['body']['providerRefreshToken']);
        $this->assertNotEquals($initialExpiry, $response['body']['providerAccessTokenExpiry']);

        // Clean up - delete the user
        $response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ]));

        $this->assertEquals(204, $response['headers']['status-code']);
    }

    public function testOAuthUnverifiedEmailCannotLinkToExistingAccount(): void
    {
        $provider = 'mock-unverified';
        $appId = '1';
        $secret = '123456';

        // First, create a user with the same email that the unverified OAuth will try to use
        $email = 'useroauthunverified@localhost.test';
        $password = 'password';

        // Clean up any existing user with this email from parallel tests
        $this->deleteUserByEmail($email);

        $response = $this->client->call(Client::METHOD_POST, '/account', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], [
            'userId' => ID::unique(),
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $existingUserId = $response['body']['$id'];

        // Enable the mock-unverified provider
        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'provider' => $provider,
            'appId' => $appId,
            'secret' => $secret,
            'enabled' => true,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        // Attempt OAuth login with unverified email - should fail because existing user has same email
        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
            'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);
        $this->assertEquals('failure', $response['body']['result']);

        // Clean up - delete the user
        $response = $this->client->call(Client::METHOD_DELETE, '/users/' . $existingUserId, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ]));

        $this->assertEquals(204, $response['headers']['status-code']);
    }

    public function testOAuthVerifiedEmailCanLinkToExistingAccount(): void
    {
        $provider = 'mock';
        $appId = '1';
        $secret = '123456';
        $email = 'useroauth@localhost.test';

        // Clean up any existing user with this email from parallel tests
        $this->deleteUserByEmail($email);

        // Create a user with the same email that the verified OAuth will try to use
        $response = $this->client->call(Client::METHOD_POST, '/account', [
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], [
            'userId' => ID::unique(),
            'email' => $email,
            'password' => 'password',
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $existingUserId = $response['body']['$id'];

        // Enable the mock provider
        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'provider' => $provider,
            'appId' => $appId,
            'secret' => $secret,
            'enabled' => true,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        // Attempt OAuth login with verified email - should succeed and link to existing account
        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
            'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals('success', $response['body']['result']);

        // Verify the OAuth identity was linked to the existing user
        $sessionCookieKey = 'a_session_' . $this->getProject()['$id'];
        $session = $response['cookies'][$sessionCookieKey];

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals($existingUserId, $response['body']['$id']);
        $this->assertEquals($email, $response['body']['email']);

        // Clean up - delete the user
        $response = $this->client->call(Client::METHOD_DELETE, '/users/' . $existingUserId, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ]));

        $this->assertEquals(204, $response['headers']['status-code']);
    }

    public function testGetSessionByID(): void
    {
        $session = $this->createAnonymousSession();

        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertEquals('anonymous', $response['body']['provider']);

        $sessionID = $response['body']['$id'];

        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/' . $sessionID, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertEquals('anonymous', $response['body']['provider']);

        $response = $this->client->call(Client::METHOD_GET, '/account/sessions/97823askjdkasd80921371980', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(404, $response['headers']['status-code']);
    }

    public function testUpdateAccountNameSearch(): void
    {
        $data = $this->setupAccountWithUpdatedName();
        $id = $data['id'];
        $newName = 'Lorem';

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_GET, '/users', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ], [
            'search' => $newName,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['users']);
        // In parallel execution, there may be more users with name 'Lorem'
        $this->assertGreaterThanOrEqual(1, count($response['body']['users']));
        // Find our user in the results
        $foundUser = null;
        foreach ($response['body']['users'] as $user) {
            if ($user['$id'] === $id) {
                $foundUser = $user;
                break;
            }
        }
        $this->assertNotNull($foundUser, 'User should be found in search results');
        $this->assertEquals($newName, $foundUser['name']);

        $response = $this->client->call(Client::METHOD_GET, '/users', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ], [
            'search' => $id,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['users']);
        $this->assertCount(1, $response['body']['users']);
        $this->assertEquals($newName, $response['body']['users'][0]['name']);
    }

    public function testUpdateAccountEmailSearch(): void
    {
        $data = $this->setupAccountWithUpdatedEmail();
        $id = $data['id'];
        $email = $data['email'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_GET, '/users', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ], [
            'search' => '"' . $email . '"',

        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['users']);
        $this->assertCount(1, $response['body']['users']);
        $this->assertEquals($response['body']['users'][0]['email'], $email);

        $response = $this->client->call(Client::METHOD_GET, '/users', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ], [
            'search' => $id,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['users']);
        $this->assertCount(1, $response['body']['users']);
        $this->assertEquals($response['body']['users'][0]['email'], $email);
    }

    public function testCreatePhone(): void
    {
        $number = '+123456789';

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/tokens/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'phone' => $number,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));

        $userId = $response['body']['userId'];

        $smsRequest = $this->getLastRequestForProject(
            $this->getProject()['$id'],
            Scope::REQUEST_TYPE_SMS,
            [
                'header_X-Username' => 'username',
                'header_X-Key' => 'password',
                'method' => 'POST',
            ],
            probe: function (array $request) use ($number) {
                $this->assertEquals('Appwrite Mock Message Sender', $request['headers']['User-Agent'] ?? null);
                $this->assertEquals('username', $request['headers']['X-Username'] ?? null);
                $this->assertEquals('password', $request['headers']['X-Key'] ?? null);
                $this->assertEquals('POST', $request['method'] ?? null);
                $this->assertEquals('+123456789', $request['data']['from'] ?? null);
                $this->assertEquals($number, $request['data']['to'] ?? null);
            }
        );

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/tokens/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique()
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);
    }

    public function testCreateSessionWithPhone(): void
    {
        $data = $this->setupPhoneAccount();
        $id = $data['id'];
        $token = explode(" ", $data['token'])[0];
        $number = $data['number'];

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::custom('ewewe'),
            'secret' => $token,
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => $id,
            'secret' => 'sdasdasdasd',
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => $id,
            'secret' => $token,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertNotEmpty($response['body']['userId']);

        $session = $response['cookies']['a_session_' . $this->getProject()['$id']];

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['phone'], $number);
        $this->assertTrue($response['body']['phoneVerification']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => $id,
            'secret' => $token,
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testConvertPhoneToPassword(): void
    {
        $data = $this->setupPhoneSession();
        $session = $data['session'];
        $email = uniqid() . 'new@localhost.test';
        $password = 'new-password';

        $response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['email'], $email);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => $password,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
    }

    public function testUpdatePhone(): void
    {
        $data = $this->setupPhoneConvertedToPassword();
        $newPhone = '+456' . substr(str_replace('.', '', microtime(true)) . getmypid() . random_int(100, 999), -8);
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'phone' => $newPhone,
            'password' => 'new-password'
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['phone'], $newPhone);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), []);

        $this->assertEquals(400, $response['headers']['status-code']);
    }

    public function testCreateSession(): void
    {
        $data = $this->setupPhoneUpdated();

        $response = $this->client->call(Client::METHOD_POST, '/users/' . $data['id'] . '/tokens', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ], [
            'expire' => 60
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);

        $userId = $response['body']['userId'];
        $secret = $response['body']['secret'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], [
            'userId' => $userId,
            'secret' => $secret
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertEquals($data['id'], $response['body']['userId']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertNotEmpty($response['body']['expire']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertEquals('browser', $response['body']['clientType']);
        $this->assertEquals('CH', $response['body']['clientCode']);
        $this->assertEquals('Chrome', $response['body']['clientName']);

        // Forwarded User Agent with API Key
        $response = $this->client->call(Client::METHOD_POST, '/users/' . $data['id'] . '/tokens', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ], [
            'expire' => 60
        ]);

        $userId = $response['body']['userId'];
        $secret = $response['body']['secret'];

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
            'x-forwarded-user-agent' => 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
        ], [
            'userId' => $userId,
            'secret' => $secret
        ]);

        $this->assertEquals('browser', $response['body']['clientType']);
        $this->assertEquals('CM', actual: $response['body']['clientCode']);
        $this->assertEquals('Chrome Mobile', $response['body']['clientName']);

        // Forwarded User Agent without API Key
        $response = $this->client->call(Client::METHOD_POST, '/users/' . $data['id'] . '/tokens', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-appwrite-key' => $this->getProject()['apiKey'],
        ], [
            'expire' => 60
        ]);

        $userId = $response['body']['userId'];
        $secret = $response['body']['secret'];

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'x-forwarded-user-agent' => 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
        ], [
            'userId' => $userId,
            'secret' => $secret
        ]);

        $this->assertEquals('browser', $response['body']['clientType']);
        $this->assertEquals('CH', $response['body']['clientCode']);
        $this->assertEquals('Chrome', $response['body']['clientName']);

        /**
         * Test for FAILURE
         */
        // Invalid userId
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], [
            'userId' => ID::custom('ewewe'),
            'secret' => $secret,
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);

        // Invalid secret
        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', [
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], [
            'userId' => $userId,
            'secret' => '123456',
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testPhoneVerification(): void
    {
        $data = $this->setupPhoneUpdated();
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertNotEmpty($response['body']['$createdAt']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));

        $tokenCreatedAt = $response['body']['$createdAt'];

        $phone = $data['phone'] ?? '';
        $smsQuery = [
            'header_X-Username' => 'username',
            'header_X-Key' => 'password',
            'method' => 'POST',
        ];

        $smsRequest = $this->getLastRequestForProject(
            $this->getProject()['$id'],
            Scope::REQUEST_TYPE_SMS,
            $smsQuery,
            probe: function (array $request) use ($tokenCreatedAt, $phone) {
                $this->assertArrayHasKey('data', $request);
                $this->assertArrayHasKey('time', $request);
                $this->assertArrayHasKey('message', $request['data'], "Last request missing message: " . \json_encode($request));
                if (!empty($phone)) {
                    $this->assertEquals($phone, $request['data']['to'] ?? null);
                }

                // Ensure we are not using token from last sms login
                $tokenRecievedAt = $request['time'];
                $this->assertGreaterThan($tokenCreatedAt, $tokenRecievedAt);
            }
        );

        /**
         * Test for FAILURE
         */

        // disable phone sessions
        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => 'console',
            'x-appwrite-response-format' => '1.9.1',
            'cookie' => 'a_session_console=' . $this->getRoot()['session'],
        ]), [
            'status' => false,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals(false, $response['body']['authPhone']);

        $response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(501, $response['headers']['status-code']);
        $this->assertEquals("Phone authentication is disabled for this project", $response['body']['message']);

        // Re-enable phone auth so other parallel tests are not affected
        $this->ensurePhoneAuthEnabled();
    }

    public function testUpdatePhoneVerification(): void
    {
        $data = $this->setupPhoneVerification();
        $id = $data['id'];
        $session = $data['session'];
        $secret = $data['token'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/verification/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'userId' => $id,
            'secret' => $secret,
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/verification/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'userId' => ID::custom('ewewe'),
            'secret' => $secret,
        ]);

        $this->assertEquals(404, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PUT, '/account/verification/phone', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'userId' => $id,
            'secret' => '999999',
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testCreateMagicUrl(): void
    {
        // Use uniqid for uniqueness in parallel test execution
        $email = 'magic-' . uniqid() . '-' . \time() . '@appwrite.io';

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            // 'url' => 'http://localhost/magiclogin',
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertEmpty($response['body']['secret']);
        $this->assertEmpty($response['body']['phrase']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));

        $userId = $response['body']['userId'];

        $lastEmail = $this->getLastEmailByAddress($email);
        $this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
        $this->assertEquals($this->getProject()['name'] . ' Login', $lastEmail['subject']);
        $this->assertStringContainsStringIgnoringCase('Sign in to '. $this->getProject()['name'] . ' with your secure link. Expires in 1 hour.', $lastEmail['text']);
        $this->assertStringNotContainsStringIgnoringCase('security phrase', $lastEmail['text']);

        $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64);

        $expireTime = strpos($lastEmail['text'], 'expire=' . urlencode($response['body']['expire']), 0);

        $this->assertNotFalse($expireTime);

        $secretTest = strpos($lastEmail['text'], 'secret=' . $response['body']['secret'], 0);

        $this->assertNotFalse($secretTest);

        $userIDTest = strpos($lastEmail['text'], 'userId=' . $response['body']['userId'], 0);

        $this->assertNotFalse($userIDTest);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'url' => 'localhost/magiclogin',
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'url' => 'http://remotehost/magiclogin',
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
        ]);

        $this->assertEquals(400, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'phrase' => true
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertNotEmpty($response['body']['phrase']);

        $phrase = $response['body']['phrase'];
        $lastEmail = $this->getLastEmailByAddress($email, function ($email) use ($phrase) {
            $this->assertStringContainsStringIgnoringCase($phrase, $email['text']);
        });
        $this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
        $this->assertStringContainsStringIgnoringCase($phrase, $lastEmail['text']);
    }

    public function testCreateSessionWithMagicUrl(): void
    {
        $projectId = $this->getProject()['$id'];

        // Get a fresh magic URL token - the cached one may have been consumed by setupMagicUrlSession
        $email = \uniqid() . 'magicurl@localhost.test';

        $tokenResponse = $this->client->call(Client::METHOD_POST, '/account/tokens/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
        ]);

        $this->assertEquals(201, $tokenResponse['headers']['status-code']);
        $id = $tokenResponse['body']['userId'];

        $lastEmail = $this->getLastEmailByAddress($email);
        $this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
        $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64);

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $projectId,
        ]), [
            'userId' => $id,
            'secret' => $token,
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertNotEmpty($response['body']['userId']);
        $this->assertEmpty($response['body']['secret']);

        $sessionId = $response['body']['$id'];
        $session = $response['cookies']['a_session_' . $this->getProject()['$id']];

        $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]));

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['email'], $email);
        $this->assertTrue($response['body']['emailVerification']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::custom('ewewe'),
            'secret' => $token,
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => $id,
            'secret' => 'sdasdasdasd',
        ]);

        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testUpdateAccountPasswordWithMagicUrl(): void
    {
        $data = $this->setupMagicUrlSession();
        $email = $data['email'];
        $session = $data['session'];

        /**
         * Test for SUCCESS
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'password' => 'new-password'
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertIsArray($response['body']);
        $this->assertNotEmpty($response['body']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
        $this->assertEquals($response['body']['email'], $email);

        $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => 'new-password',
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertEmpty($response['body']['secret']);

        /**
         * Test for FAILURE
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]));

        $this->assertEquals(401, $response['headers']['status-code']);

        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), []);

        $this->assertEquals(400, $response['headers']['status-code']);

        /**
         * Existing user tries to update password by passing wrong old password -> SHOULD FAIL
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'password' => 'new-password',
            'oldPassword' => 'wrong-password',
        ]);
        $this->assertEquals(401, $response['headers']['status-code']);

        /**
         * Existing user tries to update password without passing old password -> SHOULD FAIL
         */
        $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
        ]), [
            'password' => 'new-password'
        ]);
        $this->assertEquals(401, $response['headers']['status-code']);
    }

    public function testCreatePushTarget(): void
    {
        $response = $this->client->call(Client::METHOD_POST, '/account/targets/push', \array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id']
        ], $this->getHeaders()), [
            'targetId' => ID::unique(),
            'identifier' => 'test-identifier',
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertEquals('test-identifier', $response['body']['identifier']);
    }

    public function testUpdatePushTarget(): void
    {
        $response = $this->client->call(Client::METHOD_POST, '/account/targets/push', \array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], $this->getHeaders()), [
            'targetId' => ID::unique(),
            'identifier' => 'test-identifier-2',
        ]);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']['$id']);
        $this->assertEquals('test-identifier-2', $response['body']['identifier']);

        $response = $this->client->call(Client::METHOD_PUT, '/account/targets/'. $response['body']['$id'] .'/push', \array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], $this->getHeaders()), [
            'identifier' => 'test-identifier-updated',
        ]);

        $this->assertEquals(200, $response['headers']['status-code']);
        $this->assertEquals('test-identifier-updated', $response['body']['identifier']);
        $this->assertEquals(false, $response['body']['expired']);
    }

    public function testMFARecoveryCodeChallenge(): void
    {
        // Generate recovery codes using existing authenticated session
        $response = $this->client->call(Client::METHOD_POST, '/account/mfa/recovery-codes', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], $this->getHeaders()), []);

        $this->assertEquals(201, $response['headers']['status-code']);
        $this->assertNotEmpty($response['body']['recoveryCodes']);
        $recoveryCodes = $response['body']['recoveryCodes'];
        $this->assertGreaterThan(0, count($recoveryCodes));

        // Create recovery code challenge
        $challenge = $this->client->call(Client::METHOD_POST, '/account/mfa/challenge', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], $this->getHeaders()), [
            'factor' => 'recoveryCode'
        ]);

        $this->assertEquals(201, $challenge['headers']['status-code']);
        $this->assertNotEmpty($challenge['body']['$id']);
        $challengeId = $challenge['body']['$id'];

        // Test SUCCESS: Verify with valid recovery code (this tests the bug fix)
        $verification = $this->client->call(Client::METHOD_PUT, '/account/mfa/challenge', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], $this->getHeaders()), [
            'challengeId' => $challengeId,
            'otp' => $recoveryCodes[0]
        ]);

        $this->assertEquals(200, $verification['headers']['status-code']);
        $this->assertArrayHasKey('factors', $verification['body']);
        $this->assertContains('recoveryCode', $verification['body']['factors']);

        // Test that the code was consumed (can't use again)
        $challenge2 = $this->client->call(Client::METHOD_POST, '/account/mfa/challenge', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], $this->getHeaders()), [
            'factor' => 'recoveryCode'
        ]);

        $this->assertEquals(201, $challenge2['headers']['status-code']);

        $verification2 = $this->client->call(Client::METHOD_PUT, '/account/mfa/challenge', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], $this->getHeaders()), [
            'challengeId' => $challenge2['body']['$id'],
            'otp' => $recoveryCodes[0] // Same code should fail
        ]);

        $this->assertEquals(401, $verification2['headers']['status-code']);

        // Test FAILURE: Invalid recovery code
        $challenge3 = $this->client->call(Client::METHOD_POST, '/account/mfa/challenge', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], $this->getHeaders()), [
            'factor' => 'recoveryCode'
        ]);

        $this->assertEquals(201, $challenge3['headers']['status-code']);

        $verification3 = $this->client->call(Client::METHOD_PUT, '/account/mfa/challenge', array_merge([
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ], $this->getHeaders()), [
            'challengeId' => $challenge3['body']['$id'],
            'otp' => 'invalid-code-123'
        ]);

        $this->assertEquals(401, $verification3['headers']['status-code']);
    }

    public function testRefreshEmailPasswordSession(): void
    {
        $email = uniqid() . 'user@localhost.test';

        $account = $this->client->call(Client::METHOD_POST, '/account', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'userId' => ID::unique(),
            'email' => $email,
            'password' => 'password',
        ]);

        $this->assertEquals(201, $account['headers']['status-code']);

        $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
        ]), [
            'email' => $email,
            'password' => 'password',
        ]);

        $this->assertEquals(201, $session['headers']['status-code']);
        $this->assertNotEmpty($session['body']['$id']);

        $sessionId = $session['body']['$id'];
        $cookie = 'a_session_' . $this->getProject()['$id'] . '=' .$session['cookies']['a_session_' . $this->getProject()['$id']];

        $session = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' =>  $cookie,
        ]));

        $this->assertEquals(200, $session['headers']['status-code']);
        $this->assertNotEmpty($session['body']['expire']);
        $expiryBefore = $session['body']['expire'];

        \sleep(3); // Small delay to ensure expiry an expand

        $session = $this->client->call(Client::METHOD_PATCH, '/account/sessions/' . $sessionId, array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' =>  $cookie,
        ]));

        $this->assertEquals(200, $session['headers']['status-code']);
        $this->assertNotEmpty($session['body']['expire']);
        $expiryAfter = $session['body']['expire'];

        $this->assertGreaterThan(\strtotime($expiryBefore), \strtotime($expiryAfter));

        $session = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
            'origin' => 'http://localhost',
            'content-type' => 'application/json',
            'x-appwrite-project' => $this->getProject()['$id'],
            'cookie' =>  $cookie,
        ]));

        $this->assertEquals(200, $session['headers']['status-code']);
        $this->assertEquals(\strtotime($expiryAfter), \strtotime($session['body']['expire']));
    }
}
