<?php

declare(strict_types=1);

namespace HyperfTest\Cases\Unit\Middleware;

use App\Model\Role;
use App\Model\Route;
use App\Model\RouteGroup;
use App\Model\RoleRouteOverride;
use App\Model\Store;
use App\Model\User;
use App\Model\UserDataScope;
use Hyperf\DbConnection\Db;
use HyperfTest\TestCase;
use Qbhy\HyperfAuth\AuthManager;

use function Hyperf\Support\make;

/**
 * @internal
 * @coversNothing
 */
class PermissionMiddlewareTest extends TestCase
{
    protected function getAuthToken(User $user): string
    {
        $auth = make(AuthManager::class);
        return $auth->guard('jwt')->login($user);
    }

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

    protected function createTestUser(string $role_name, array $overrides = []): User
    {
        $role = Role::query()->where('name', $role_name)->firstOrFail();
        $suffix = bin2hex(random_bytes(4));

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

    // ========== Step 1: 路由访问检查 ==========

    public function test_administrator_bypasses_route_check(): void
    {
        $user = $this->createTestUser('administrator');

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

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

    public function test_non_admin_without_route_group_returns_403(): void
    {
        $user = $this->createTestUser('accessor');

        // accessor 没有路由组授权，应返回 403
        $response = $this->get('/api/v1/users', [], $this->authHeaders($user));

        $response->assertStatus(403);
    }

    public function test_route_group_authorization_allows_access(): void
    {
        $user = $this->createTestUser('developer');

        $route = Route::query()
            ->where('method', 'GET')
            ->where('path', '/api/v1/users')
            ->first();

        if (!$route) {
            $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由');
        }

        // 创建路由组并授权
        $group = RouteGroup::query()->create([
            'name' => 'test_user_mgmt_' . uniqid(),
            'label' => '用户管理测试',
        ]);

        $old_group_id = $route->group_id;
        $route->group_id = $group->id;
        $route->save();

        Db::table('role_route_groups')->insert([
            'role_id' => $user->role_id,
            'group_id' => $group->id,
        ]);

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

        // 清理
        Db::table('role_route_groups')
            ->where('role_id', $user->role_id)
            ->where('group_id', $group->id)
            ->delete();
        $route->group_id = $old_group_id;
        $route->save();
        $group->delete();
    }

    public function test_override_allows_access_bypassing_group(): void
    {
        $user = $this->createTestUser('accessor');

        $route = Route::query()
            ->where('method', 'GET')
            ->where('path', '/api/v1/users')
            ->first();

        if (!$route) {
            $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由');
        }

        // 设置 override 为允许（无需路由组）
        RoleRouteOverride::query()->create([
            'role_id' => $user->role_id,
            'route_id' => $route->id,
            'allowed' => true,
        ]);

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

        // 清理
        RoleRouteOverride::query()
            ->where('role_id', $user->role_id)
            ->where('route_id', $route->id)
            ->delete();
    }

    public function test_override_deny_overrides_group_auth(): void
    {
        $user = $this->createTestUser('developer');

        $route = Route::query()
            ->where('method', 'GET')
            ->where('path', '/api/v1/users')
            ->first();

        if (!$route) {
            $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由');
        }

        // 先授权路由组
        $group = RouteGroup::query()->create([
            'name' => 'test_override_deny_' . uniqid(),
            'label' => '覆盖拒绝测试',
        ]);

        $old_group_id = $route->group_id;
        $route->group_id = $group->id;
        $route->save();

        Db::table('role_route_groups')->insert([
            'role_id' => $user->role_id,
            'group_id' => $group->id,
        ]);

        // 设置 override 为拒绝（优先级高于 group）
        RoleRouteOverride::query()->create([
            'role_id' => $user->role_id,
            'route_id' => $route->id,
            'allowed' => false,
        ]);

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

        // 清理
        RoleRouteOverride::query()
            ->where('role_id', $user->role_id)
            ->where('route_id', $route->id)
            ->delete();
        Db::table('role_route_groups')
            ->where('role_id', $user->role_id)
            ->where('group_id', $group->id)
            ->delete();
        $route->group_id = $old_group_id;
        $route->save();
        $group->delete();
    }

    // ========== Step 2: 数据范围检查 ==========

    public function test_user_without_role_returns_403(): void
    {
        $suffix = bin2hex(random_bytes(4));
        $user = User::query()->create([
            'username' => 'norole_' . $suffix,
            'email' => 'norole_' . $suffix . '@example.com',
            'password' => 'Pass_' . $suffix,
            'status' => 1,
            'role_id' => null,
        ]);

        $response = $this->get('/api/v1/users', [], $this->authHeaders($user));
        $response->assertStatus(403);
    }

    public function test_accessor_with_store_scope_and_override(): void
    {
        $user = $this->createTestUser('accessor');

        $route = Route::query()
            ->where('method', 'GET')
            ->where('path', '/api/v1/users')
            ->first();

        if (!$route) {
            $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由');
        }

        // 授权路由访问
        RoleRouteOverride::query()->create([
            'role_id' => $user->role_id,
            'route_id' => $route->id,
            'allowed' => true,
        ]);

        // 添加 store scope
        $store = Store::query()->first();
        if ($store) {
            UserDataScope::query()->create([
                'user_id' => $user->id,
                'scope_type' => 'store',
                'scope_id' => $store->id,
            ]);
        }

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

        // 清理
        RoleRouteOverride::query()
            ->where('role_id', $user->role_id)
            ->where('route_id', $route->id)
            ->delete();
        UserDataScope::query()->where('user_id', $user->id)->delete();
    }

    public function test_administrator_scope_type_is_all(): void
    {
        $user = $this->createTestUser('administrator');

        // administrator 应有 scope_type='all'，可以正常访问
        $response = $this->get('/api/v1/users', [], $this->authHeaders($user));
        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
    }

    // ========== 安全修复测试 ==========

    public function test_unregistered_route_denied_for_non_admin(): void
    {
        $user = $this->createTestUser('developer');

        // 从 routes 表删除 GET /api/v1/users 记录，模拟未注册场景
        $route = Route::query()->where('method', 'GET')->where('path', '/api/v1/users')->first();
        if (!$route) {
            $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由');
        }

        $route_data = $route->toArray();
        $route->delete();

        try {
            // 白名单模式：routes 表中无记录 → 403
            $response = $this->get('/api/v1/users', [], $this->authHeaders($user));
            $response->assertStatus(403);
        } finally {
            // 恢复路由记录（使用 Db::table 保留原 id）
            Db::table('routes')->insert($route_data);
        }
    }

    public function test_admin_allowed_on_unregistered_route(): void
    {
        $user = $this->createTestUser('administrator');

        // 从 routes 表删除 GET /api/v1/users 记录
        $route = Route::query()->where('method', 'GET')->where('path', '/api/v1/users')->first();
        if (!$route) {
            $this->markTestSkipped('routes 表中无 GET /api/v1/users 路由');
        }

        $route_data = $route->toArray();
        $route->delete();

        try {
            // administrator 跳过路由检查，即使路由未在 routes 表中也应正常访问
            $response = $this->get('/api/v1/users', [], $this->authHeaders($user));
            $response->assertStatus(200);
        } finally {
            // 恢复路由记录（使用 Db::table 保留原 id）
            Db::table('routes')->insert($route_data);
        }
    }

    public function test_parametric_route_matches_template_path(): void
    {
        $user = $this->createTestUser('developer');

        $route = Route::query()
            ->where('method', 'GET')
            ->where('path', '/api/v1/users/{id}')
            ->first();

        if (!$route) {
            $this->markTestSkipped('routes 表中无 GET /api/v1/users/{id} 路由');
        }

        // 创建路由组并授权
        $group = RouteGroup::query()->create([
            'name' => 'test_param_route_' . uniqid(),
            'label' => '参数化路由测试',
        ]);

        $old_group_id = $route->group_id;
        $route->group_id = $group->id;
        $route->save();

        Db::table('role_route_groups')->insert([
            'role_id' => $user->role_id,
            'group_id' => $group->id,
        ]);

        // 访问参数化路由 /api/v1/users/1（应匹配模板路径 /api/v1/users/{id}）
        $response = $this->get('/api/v1/users/1', [], $this->authHeaders($user));
        // 路由检查应通过（200 或 404 取决于用户是否存在，但不应是 403）
        $this->assertNotEquals(403, $response->getStatusCode());

        // 清理
        Db::table('role_route_groups')
            ->where('role_id', $user->role_id)
            ->where('group_id', $group->id)
            ->delete();
        $route->group_id = $old_group_id;
        $route->save();
        $group->delete();
    }
}
