<?php

declare(strict_types=1);

namespace HyperfTest\Cases\Integration\Auth;

use App\Model\User;
use HyperfTest\TestCase;
use Qbhy\HyperfAuth\AuthManager;

use function Hyperf\Support\make;

/**
 * @internal
 * @coversNothing
 */
class AuthControllerTest extends TestCase
{
    /**
     * 获取认证用的 JWT Token
     */
    protected function getAuthToken(?User $user = null): string
    {
        if (!$user) {
            $user = $this->fetchUser(static function ($query): void {
                $query->where('status', 1);
            });
        }
        if (!$user) {
            $this->markTestSkipped('没有可用的活跃用户，无法测试');
        }

        $auth = make(AuthManager::class);
        return $auth->guard('jwt')->login($user);
    }

    protected function authHeaders(?User $user = null): array
    {
        return ['Authorization' => 'Bearer ' . $this->getAuthToken($user)];
    }

    protected function fetchUser(?callable $callback = null): ?User
    {
        if (\Swoole\Coroutine::getCid() > 0) {
            $query = User::query();
            if ($callback !== null) {
                $callback($query);
            }

            return $query->first();
        }

        $user = null;

        \Swoole\Coroutine\run(static function () use ($callback, &$user): void {
            $query = User::query();
            if ($callback !== null) {
                $callback($query);
            }

            $user = $query->first();
        });

        return $user;
    }

    protected function createTestUser(array $overrides = []): User
    {
        $suffix = bin2hex(random_bytes(4));

        return User::query()->create(array_merge([
            'username' => 'auth_test_' . $suffix,
            'password' => 'Pass_' . $suffix,
            'email' => 'auth_test_' . $suffix . '@example.com',
            'status' => 1,
        ], $overrides));
    }

    // ========== 注册接口参数校验 ==========

    public function test_register_success(): void
    {
        $suffix = bin2hex(random_bytes(4));

        $response = $this->post('/api/v1/register', [
            'username' => 'reg_' . $suffix,
            'password' => 'Pass_' . $suffix,
            'email' => 'reg_' . $suffix . '@example.com',
        ]);

        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
        $response->assertJsonPath('data.username', 'reg_' . $suffix);
    }

    public function test_register_missing_username_returns_400(): void
    {
        $response = $this->post('/api/v1/register', [
            'password' => 'Pass_1234',
            'email' => 'test@example.com',
        ]);

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_register_short_username_returns_400(): void
    {
        $response = $this->post('/api/v1/register', [
            'username' => 'ab',
            'password' => 'Pass_1234',
            'email' => 'test_short@example.com',
        ]);

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_register_missing_password_returns_400(): void
    {
        $suffix = bin2hex(random_bytes(4));

        $response = $this->post('/api/v1/register', [
            'username' => 'reg_' . $suffix,
            'email' => 'reg_' . $suffix . '@example.com',
        ]);

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_register_short_password_returns_400(): void
    {
        $suffix = bin2hex(random_bytes(4));

        $response = $this->post('/api/v1/register', [
            'username' => 'reg_' . $suffix,
            'password' => '12345',
            'email' => 'reg_' . $suffix . '@example.com',
        ]);

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_register_invalid_email_returns_400(): void
    {
        $suffix = bin2hex(random_bytes(4));

        $response = $this->post('/api/v1/register', [
            'username' => 'reg_' . $suffix,
            'password' => 'Pass_1234',
            'email' => 'not-an-email',
        ]);

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_register_duplicate_username_returns_400(): void
    {
        $user = $this->createTestUser();

        $response = $this->post('/api/v1/register', [
            'username' => $user->username,
            'password' => 'Pass_1234',
            'email' => 'dup_' . bin2hex(random_bytes(4)) . '@example.com',
        ]);

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    // ========== 登录接口参数校验 ==========

    public function test_login_success(): void
    {
        $password = 'Login_test_pass';
        $user = $this->createTestUser(['password' => $password]);

        $response = $this->post('/api/v1/login', [
            'username' => $user->username,
            'password' => $password,
        ]);

        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
        $response->assertJsonStructure([
            'data' => ['access_token', 'refresh_token', 'token_type', 'expires_in', 'user'],
        ]);
    }

    public function test_login_missing_username_returns_400(): void
    {
        $response = $this->post('/api/v1/login', [
            'password' => 'Pass_1234',
        ]);

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_login_missing_password_returns_400(): void
    {
        $response = $this->post('/api/v1/login', [
            'username' => 'some_user',
        ]);

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_login_wrong_password_returns_401(): void
    {
        $user = $this->createTestUser();

        $response = $this->post('/api/v1/login', [
            'username' => $user->username,
            'password' => 'wrong_password_here',
        ]);

        $response->assertStatus(401);
        $response->assertJsonPath('code', 401);
    }

    public function test_login_disabled_user_returns_403(): void
    {
        $password = 'Disabled_pass';
        $user = $this->createTestUser(['password' => $password, 'status' => 0]);

        $response = $this->post('/api/v1/login', [
            'username' => $user->username,
            'password' => $password,
        ]);

        $response->assertStatus(403);
        $response->assertJsonPath('code', 403);
    }

    // ========== 密码修改接口 ==========

    public function test_change_password_success(): void
    {
        $old_password = 'OldPass_1234';
        $new_password = 'NewPass_5678';
        $user = $this->createTestUser(['password' => $old_password]);

        $response = $this->put('/api/v1/me/password', [
            'old_password' => $old_password,
            'new_password' => $new_password,
        ], $this->authHeaders($user));

        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);

        // 验证 refresh_token 已被清除
        $user->refresh();
        $this->assertNull($user->refresh_token);
        $this->assertNull($user->refresh_token_expires_at);
    }

    public function test_change_password_clears_refresh_token(): void
    {
        $old_password = 'OldPass_clear';
        $user = $this->createTestUser(['password' => $old_password]);

        // 先登录获取 refresh_token
        $this->post('/api/v1/login', [
            'username' => $user->username,
            'password' => $old_password,
        ]);

        $user->refresh();
        $this->assertNotNull($user->refresh_token);

        // 修改密码
        $response = $this->put('/api/v1/me/password', [
            'old_password' => $old_password,
            'new_password' => 'NewPass_clear',
        ], $this->authHeaders($user));

        $response->assertStatus(200);

        $user->refresh();
        $this->assertNull($user->refresh_token);
    }

    public function test_change_password_wrong_old_password_returns_400(): void
    {
        $user = $this->createTestUser(['password' => 'CorrectOld_1']);

        $response = $this->put('/api/v1/me/password', [
            'old_password' => 'WrongOld_pass',
            'new_password' => 'NewPass_5678',
        ], $this->authHeaders($user));

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_change_password_short_new_password_returns_400(): void
    {
        $old_password = 'OldPass_short';
        $user = $this->createTestUser(['password' => $old_password]);

        $response = $this->put('/api/v1/me/password', [
            'old_password' => $old_password,
            'new_password' => '12345',
        ], $this->authHeaders($user));

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_change_password_missing_old_password_returns_400(): void
    {
        $user = $this->createTestUser();

        $response = $this->put('/api/v1/me/password', [
            'new_password' => 'NewPass_5678',
        ], $this->authHeaders($user));

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_change_password_without_token_returns_401(): void
    {
        $response = $this->put('/api/v1/me/password', [
            'old_password' => 'old',
            'new_password' => 'new_pass',
        ]);

        $response->assertStatus(401);
    }

    // ========== 个人信息更新接口 ==========

    public function test_update_profile_email(): void
    {
        $user = $this->createTestUser();
        $new_email = 'profile_' . bin2hex(random_bytes(4)) . '@example.com';

        $response = $this->put('/api/v1/me/profile', [
            'email' => $new_email,
        ], $this->authHeaders($user));

        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
        $response->assertJsonPath('data.email', $new_email);

        $user->refresh();
        $this->assertSame($new_email, $user->email);
    }

    public function test_update_profile_ext(): void
    {
        $user = $this->createTestUser();
        $ext = ['nickname' => 'TestNick', 'theme' => 'dark'];

        $response = $this->put('/api/v1/me/profile', [
            'ext' => $ext,
        ], $this->authHeaders($user));

        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
        $response->assertJsonPath('data.ext.nickname', 'TestNick');
    }

    public function test_update_profile_invalid_email_returns_400(): void
    {
        $user = $this->createTestUser();

        $response = $this->put('/api/v1/me/profile', [
            'email' => 'not-an-email',
        ], $this->authHeaders($user));

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_update_profile_duplicate_email_returns_400(): void
    {
        $existing = $this->createTestUser();
        $user = $this->createTestUser();

        $response = $this->put('/api/v1/me/profile', [
            'email' => $existing->email,
        ], $this->authHeaders($user));

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_update_profile_no_fields_returns_400(): void
    {
        $user = $this->createTestUser();

        $response = $this->put('/api/v1/me/profile', [], $this->authHeaders($user));

        $response->assertStatus(400);
        $response->assertJsonPath('code', 400);
    }

    public function test_update_profile_without_token_returns_401(): void
    {
        $response = $this->put('/api/v1/me/profile', [
            'email' => 'test@example.com',
        ]);

        $response->assertStatus(401);
    }

    // ========== 现有接口测试 ==========

    public function test_me_returns_user_info(): void
    {
        $user = $this->createTestUser();

        $response = $this->get('/api/v1/me', [], $this->authHeaders($user));

        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
        $response->assertJsonPath('data.id', $user->id);
        $response->assertJsonPath('data.username', $user->username);
        $response->assertJsonStructure([
            'data' => ['id', 'username', 'email', 'status', 'ext', 'created_at'],
        ]);
    }

    public function test_me_without_token_returns_401(): void
    {
        $response = $this->get('/api/v1/me');

        $response->assertStatus(401);
    }

    public function test_logout_clears_refresh_token(): void
    {
        $password = 'LogoutTest_1';
        $user = $this->createTestUser(['password' => $password]);

        // 先登录获取 refresh_token
        $this->post('/api/v1/login', [
            'username' => $user->username,
            'password' => $password,
        ]);

        $user->refresh();
        $this->assertNotNull($user->refresh_token);

        // 退出登录
        $response = $this->get('/api/v1/logout', [], $this->authHeaders($user));

        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);

        $user->refresh();
        $this->assertNull($user->refresh_token);
    }
}
