<?php

declare(strict_types=1);

namespace HyperfTest\Cases\Integration\Permission;

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

use function Hyperf\Support\make;

/**
 * 权限管理 API 集成测试（P3.4）
 *
 * @internal
 * @coversNothing
 */
class PermissionApiTest extends TestCase
{
    // ========== Helpers ==========

    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_api_' . $suffix,
            'email' => 'perm_api_' . $suffix . '@example.com',
            'password' => 'Pass_' . $suffix,
            'status' => 1,
            'role_id' => $role->id,
        ], $overrides));
    }

    protected function getAdmin(): User
    {
        $admin_role = Role::query()->where('name', 'administrator')->firstOrFail();
        $user = User::query()->where('status', 1)->where('role_id', $admin_role->id)->first();
        if (!$user) {
            $user = $this->createTestUser('administrator');
        }
        return $user;
    }

    // ========== 路由组 CRUD ==========

    public function test_admin_can_list_route_groups(): void
    {
        $admin = $this->getAdmin();

        $response = $this->get('/api/v1/route-groups', [], $this->authHeaders($admin));
        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
    }

    public function test_admin_can_create_route_group(): void
    {
        $admin = $this->getAdmin();
        $name = 'test_group_' . bin2hex(random_bytes(4));

        $response = $this->post('/api/v1/route-groups', [
            'name' => $name,
            'label' => '测试路由组',
            'description' => '测试用',
            'sort_order' => 10,
        ], $this->authHeaders($admin));

        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
        $response->assertJsonPath('data.name', $name);
        $response->assertJsonPath('data.sort_order', 10);

        // 清理
        $body = json_decode($response->getContent(), true);
        RouteGroup::query()->find($body['data']['id'])?->delete();
    }

    public function test_create_route_group_duplicate_name_returns_400(): void
    {
        $admin = $this->getAdmin();
        $name = 'dup_group_' . bin2hex(random_bytes(4));

        $group = RouteGroup::query()->create(['name' => $name, 'label' => 'dup']);

        try {
            $response = $this->post('/api/v1/route-groups', [
                'name' => $name,
                'label' => '重复',
            ], $this->authHeaders($admin));

            $response->assertStatus(400);
        } finally {
            $group->delete();
        }
    }

    public function test_admin_can_update_route_group(): void
    {
        $admin = $this->getAdmin();
        $group = RouteGroup::query()->create([
            'name' => 'upd_group_' . bin2hex(random_bytes(4)),
            'label' => '原始',
        ]);

        try {
            $response = $this->put('/api/v1/route-groups/' . $group->id, [
                'label' => '已更新',
                'sort_order' => 5,
            ], $this->authHeaders($admin));

            $response->assertStatus(200);
            $response->assertJsonPath('code', 0);
            $response->assertJsonPath('data.label', '已更新');
            $response->assertJsonPath('data.sort_order', 5);
        } finally {
            $group->delete();
        }
    }

    public function test_admin_can_delete_route_group(): void
    {
        $admin = $this->getAdmin();
        $group = RouteGroup::query()->create([
            'name' => 'del_group_' . bin2hex(random_bytes(4)),
            'label' => '待删除',
        ]);

        $response = $this->delete('/api/v1/route-groups/' . $group->id, [], $this->authHeaders($admin));

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

        $this->assertNull(RouteGroup::query()->find($group->id));
    }

    public function test_delete_nonexistent_group_returns_404(): void
    {
        $admin = $this->getAdmin();

        $response = $this->delete('/api/v1/route-groups/999999', [], $this->authHeaders($admin));
        $response->assertStatus(404);
    }

    // ========== 路由列表与分组分配 ==========

    public function test_admin_can_list_routes(): void
    {
        $admin = $this->getAdmin();

        $response = $this->get('/api/v1/routes', [], $this->authHeaders($admin));
        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
    }

    public function test_admin_can_filter_ungrouped_routes(): void
    {
        $admin = $this->getAdmin();

        $response = $this->get('/api/v1/routes', ['group_id' => 'ungrouped'], $this->authHeaders($admin));
        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);

        // 验证返回的路由都没有 group_id
        $body = json_decode($response->getContent(), true);
        foreach ($body['data'] as $route) {
            $this->assertNull($route['group_id']);
        }
    }

    public function test_admin_can_assign_route_to_group(): void
    {
        $admin = $this->getAdmin();

        $route = Route::query()->first();
        if (!$route) {
            $this->markTestSkipped('routes 表中无数据');
        }

        $group = RouteGroup::query()->create([
            'name' => 'assign_grp_' . bin2hex(random_bytes(4)),
            'label' => '分配测试',
        ]);

        $old_group_id = $route->group_id;

        try {
            $response = $this->put('/api/v1/routes/' . $route->id . '/group', [
                'group_id' => $group->id,
            ], $this->authHeaders($admin));

            $response->assertStatus(200);
            $response->assertJsonPath('code', 0);
            $response->assertJsonPath('data.group_id', $group->id);
        } finally {
            $route->group_id = $old_group_id;
            $route->save();
            $group->delete();
        }
    }

    public function test_admin_can_remove_route_from_group(): void
    {
        $admin = $this->getAdmin();

        $route = Route::query()->whereNotNull('group_id')->first();
        if (!$route) {
            $this->markTestSkipped('routes 表中无已分组路由');
        }

        $old_group_id = $route->group_id;

        try {
            $response = $this->put('/api/v1/routes/' . $route->id . '/group', [
                'group_id' => null,
            ], $this->authHeaders($admin));

            $response->assertStatus(200);
            $response->assertJsonPath('data.group_id', null);
        } finally {
            $route->group_id = $old_group_id;
            $route->save();
        }
    }

    // ========== 角色路由授权 ==========

    public function test_admin_can_get_role_route_groups(): void
    {
        $admin = $this->getAdmin();
        $role = Role::query()->where('name', 'developer')->firstOrFail();

        $response = $this->get('/api/v1/roles/' . $role->id . '/route-groups', [], $this->authHeaders($admin));
        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
    }

    public function test_admin_can_set_role_route_groups(): void
    {
        $admin = $this->getAdmin();
        $role = Role::query()->where('name', 'developer')->firstOrFail();

        $group = RouteGroup::query()->create([
            'name' => 'auth_grp_' . bin2hex(random_bytes(4)),
            'label' => '授权测试',
        ]);

        try {
            $response = $this->put('/api/v1/roles/' . $role->id . '/route-groups', [
                'group_ids' => [$group->id],
            ], $this->authHeaders($admin));

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

            $body = json_decode($response->getContent(), true);
            $returned_ids = array_column($body['data'], 'id');
            $this->assertContains($group->id, $returned_ids);
        } finally {
            // 清理授权
            $role->routeGroups()->detach($group->id);
            $group->delete();
        }
    }

    public function test_set_admin_route_groups_returns_400(): void
    {
        $admin = $this->getAdmin();
        $admin_role = Role::query()->where('name', 'administrator')->firstOrFail();

        $response = $this->put('/api/v1/roles/' . $admin_role->id . '/route-groups', [
            'group_ids' => [1],
        ], $this->authHeaders($admin));

        $response->assertStatus(400);
    }

    public function test_admin_can_get_role_route_overrides(): void
    {
        $admin = $this->getAdmin();
        $role = Role::query()->where('name', 'accessor')->firstOrFail();

        $response = $this->get('/api/v1/roles/' . $role->id . '/route-overrides', [], $this->authHeaders($admin));
        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
    }

    public function test_admin_can_set_role_route_overrides(): void
    {
        $admin = $this->getAdmin();
        $role = Role::query()->where('name', 'accessor')->firstOrFail();
        $route = Route::query()->first();

        if (!$route) {
            $this->markTestSkipped('routes 表中无数据');
        }

        try {
            $response = $this->put('/api/v1/roles/' . $role->id . '/route-overrides', [
                'overrides' => [
                    ['route_id' => $route->id, 'allowed' => true],
                ],
            ], $this->authHeaders($admin));

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

            // 验证数据库
            $override = RoleRouteOverride::query()
                ->where('role_id', $role->id)
                ->where('route_id', $route->id)
                ->first();
            $this->assertNotNull($override);
            $this->assertTrue($override->allowed);
        } finally {
            RoleRouteOverride::query()
                ->where('role_id', $role->id)
                ->where('route_id', $route->id)
                ->delete();
        }
    }

    public function test_set_route_overrides_duplicate_route_id_returns_400(): void
    {
        $admin = $this->getAdmin();
        $role = Role::query()->where('name', 'accessor')->firstOrFail();
        $route = Route::query()->first();

        if (!$route) {
            $this->markTestSkipped('routes 表中无数据');
        }

        $response = $this->put('/api/v1/roles/' . $role->id . '/route-overrides', [
            'overrides' => [
                ['route_id' => $route->id, 'allowed' => true],
                ['route_id' => $route->id, 'allowed' => false],
            ],
        ], $this->authHeaders($admin));

        $response->assertStatus(400);
    }

    // ========== 用户数据范围 ==========

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

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

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

        $body = json_decode($response->getContent(), true);
        $this->assertSame($user->id, $body['data']['user_id']);
        $this->assertArrayHasKey('scopes', $body['data']);
        $this->assertArrayHasKey('resolved_store_ids', $body['data']);
    }

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

        try {
            $response = $this->put('/api/v1/users/' . $user->id . '/data-scope', [
                'scopes' => [
                    ['scope_type' => 'company', 'scope_id' => 1],
                    ['scope_type' => 'store', 'scope_id' => 1],
                ],
            ], $this->authHeaders($admin));

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

            // 验证数据库
            $scopes = UserDataScope::query()->where('user_id', $user->id)->get();
            $this->assertCount(2, $scopes);
        } finally {
            UserDataScope::query()->where('user_id', $user->id)->delete();
        }
    }

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

        // 先设置一些 scope
        UserDataScope::query()->create([
            'user_id' => $user->id,
            'scope_type' => 'store',
            'scope_id' => 1,
        ]);

        $response = $this->put('/api/v1/users/' . $user->id . '/data-scope', [
            'scopes' => [],
        ], $this->authHeaders($admin));

        $response->assertStatus(200);

        $scopes = UserDataScope::query()->where('user_id', $user->id)->get();
        $this->assertCount(0, $scopes);
    }

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

        $response = $this->put('/api/v1/users/' . $user->id . '/data-scope', [
            'scopes' => [
                ['scope_type' => 'invalid', 'scope_id' => 1],
            ],
        ], $this->authHeaders($admin));

        $response->assertStatus(400);
    }

    // ========== 角色管理 ==========

    public function test_admin_can_list_roles(): void
    {
        $admin = $this->getAdmin();

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

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

        $body = json_decode($response->getContent(), true);
        $this->assertGreaterThanOrEqual(3, count($body['data'])); // administrator, developer, accessor
    }

    public function test_admin_can_assign_role(): void
    {
        $admin = $this->getAdmin();
        $user = $this->createTestUser('accessor');
        $developer_role = Role::query()->where('name', 'developer')->firstOrFail();

        $old_role_id = $user->role_id;

        try {
            $response = $this->put('/api/v1/users/' . $user->id . '/role', [
                'role_id' => $developer_role->id,
            ], $this->authHeaders($admin));

            $response->assertStatus(200);
            $response->assertJsonPath('code', 0);
            $response->assertJsonPath('data.role_id', $developer_role->id);
        } finally {
            $user->role_id = $old_role_id;
            $user->save();
        }
    }

    public function test_admin_cannot_downgrade_self(): void
    {
        $admin = $this->getAdmin();
        $accessor_role = Role::query()->where('name', 'accessor')->firstOrFail();

        $response = $this->put('/api/v1/users/' . $admin->id . '/role', [
            'role_id' => $accessor_role->id,
        ], $this->authHeaders($admin));

        $response->assertStatus(400);
    }

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

        $response = $this->put('/api/v1/users/' . $user->id . '/role', [
            'role_id' => 999999,
        ], $this->authHeaders($admin));

        $response->assertStatus(404);
    }

    // ========== 基础数据接口 ==========

    public function test_admin_can_get_companies(): void
    {
        $admin = $this->getAdmin();

        $response = $this->get('/api/v1/companies', [], $this->authHeaders($admin));
        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
    }

    public function test_admin_can_get_platforms(): void
    {
        $admin = $this->getAdmin();

        $response = $this->get('/api/v1/platforms', [], $this->authHeaders($admin));
        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
    }

    public function test_admin_can_get_stores(): void
    {
        $admin = $this->getAdmin();

        $response = $this->get('/api/v1/stores', [], $this->authHeaders($admin));
        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
    }

    public function test_stores_support_company_filter(): void
    {
        $admin = $this->getAdmin();

        $response = $this->get('/api/v1/stores', ['company_id' => 1], $this->authHeaders($admin));
        $response->assertStatus(200);
        $response->assertJsonPath('code', 0);
    }

    // ========== 认证拦截 ==========

    public function test_unauthenticated_returns_401(): void
    {
        $response = $this->get('/api/v1/route-groups');
        $response->assertStatus(401);

        $response = $this->get('/api/v1/roles');
        $response->assertStatus(401);

        $response = $this->get('/api/v1/companies');
        $response->assertStatus(401);
    }
}
