<?php

declare(strict_types=1);

namespace HyperfTest\Cases\Unit\Model;

use App\Model\Role;
use App\Model\Route;
use App\Model\RouteGroup;
use App\Model\RoleRouteOverride;
use Hyperf\DbConnection\Db;
use PHPUnit\Framework\TestCase;

/**
 * @internal
 * @coversNothing
 */
class RoutePermissionModelTest extends TestCase
{
    protected function runInCoroutine(callable $callback): void
    {
        if (\Swoole\Coroutine::getCid() > 0) {
            $callback();
            return;
        }

        $exception = null;
        \Swoole\Coroutine\run(static function () use ($callback, &$exception): void {
            try {
                $callback();
            } catch (\Throwable $e) {
                $exception = $e;
            }
        });
        if ($exception) {
            throw $exception;
        }
    }

    // --- RouteGroup 模型测试 ---

    public function test_route_group_fillable(): void
    {
        $group = new RouteGroup();
        $this->assertEqualsCanonicalizing(
            ['name', 'label', 'description', 'sort_order'],
            $group->getFillable()
        );
    }

    public function test_route_group_table_name(): void
    {
        $group = new RouteGroup();
        $this->assertSame('route_groups', $group->getTable());
    }

    public function test_route_group_has_no_timestamps(): void
    {
        $group = new RouteGroup();
        $this->assertFalse($group->timestamps);
    }

    // --- Route 模型测试 ---

    public function test_route_fillable(): void
    {
        $route = new Route();
        $this->assertEqualsCanonicalizing(
            ['group_id', 'method', 'path', 'name', 'label'],
            $route->getFillable()
        );
    }

    public function test_route_table_name(): void
    {
        $route = new Route();
        $this->assertSame('routes', $route->getTable());
    }

    public function test_route_has_no_timestamps(): void
    {
        $route = new Route();
        $this->assertFalse($route->timestamps);
    }

    // --- RoleRouteOverride 模型测试 ---

    public function test_role_route_override_fillable(): void
    {
        $override = new RoleRouteOverride();
        $this->assertEqualsCanonicalizing(
            ['role_id', 'route_id', 'allowed'],
            $override->getFillable()
        );
    }

    public function test_role_route_override_table_name(): void
    {
        $override = new RoleRouteOverride();
        $this->assertSame('role_route_overrides', $override->getTable());
    }

    public function test_role_route_override_casts_allowed_to_boolean(): void
    {
        $override = new RoleRouteOverride();
        $casts = $override->getCasts();
        $this->assertSame('boolean', $casts['allowed']);
    }

    // --- 关联测试（需要数据库） ---

    public function test_route_group_has_many_routes(): void
    {
        $this->runInCoroutine(function (): void {
            Db::beginTransaction();
            try {
                $group = RouteGroup::query()->create([
                    'name' => 'test_group_' . uniqid(),
                    'label' => '测试分组',
                ]);

                Route::query()->create([
                    'group_id' => $group->id,
                    'method' => 'GET',
                    'path' => '/api/v1/test-' . uniqid(),
                ]);
                Route::query()->create([
                    'group_id' => $group->id,
                    'method' => 'POST',
                    'path' => '/api/v1/test-' . uniqid(),
                ]);

                $routes = $group->routes;
                $this->assertCount(2, $routes);
            } finally {
                Db::rollBack();
            }
        });
    }

    public function test_route_belongs_to_group(): void
    {
        $this->runInCoroutine(function (): void {
            Db::beginTransaction();
            try {
                $group = RouteGroup::query()->create([
                    'name' => 'test_group_' . uniqid(),
                    'label' => '测试分组',
                ]);

                $route = Route::query()->create([
                    'group_id' => $group->id,
                    'method' => 'GET',
                    'path' => '/api/v1/test-' . uniqid(),
                ]);

                $this->assertNotNull($route->group);
                $this->assertSame($group->id, $route->group->id);
            } finally {
                Db::rollBack();
            }
        });
    }

    public function test_role_has_many_route_groups(): void
    {
        $this->runInCoroutine(function (): void {
            Db::beginTransaction();
            try {
                $role = Role::query()->where('name', 'developer')->first();

                $group1 = RouteGroup::query()->create([
                    'name' => 'test_group_a_' . uniqid(),
                    'label' => '测试分组 A',
                ]);
                $group2 = RouteGroup::query()->create([
                    'name' => 'test_group_b_' . uniqid(),
                    'label' => '测试分组 B',
                ]);

                $role->routeGroups()->attach([$group1->id, $group2->id]);

                // 重新加载关联
                $role->load('routeGroups');
                $this->assertCount(2, $role->routeGroups);

                $group_ids = $role->routeGroups->pluck('id')->toArray();
                $this->assertContains($group1->id, $group_ids);
                $this->assertContains($group2->id, $group_ids);
            } finally {
                Db::rollBack();
            }
        });
    }

    public function test_role_route_override_query(): void
    {
        $this->runInCoroutine(function (): void {
            Db::beginTransaction();
            try {
                $role = Role::query()->where('name', 'accessor')->first();

                $route = Route::query()->create([
                    'method' => 'DELETE',
                    'path' => '/api/v1/test-override-' . uniqid(),
                ]);

                RoleRouteOverride::query()->create([
                    'role_id' => $role->id,
                    'route_id' => $route->id,
                    'allowed' => false,
                ]);

                // 查询角色的覆盖规则
                $overrides = $role->routeOverrides;
                $this->assertGreaterThanOrEqual(1, $overrides->count());

                $override = $overrides->where('route_id', $route->id)->first();
                $this->assertNotNull($override);
                $this->assertFalse($override->allowed);

                // 查询路由的覆盖规则
                $route_overrides = $route->overrides;
                $this->assertCount(1, $route_overrides);
                $this->assertSame($role->id, $route_overrides->first()->role_id);
            } finally {
                Db::rollBack();
            }
        });
    }

    public function test_route_method_path_unique_constraint(): void
    {
        $this->runInCoroutine(function (): void {
            Db::beginTransaction();
            try {
                $unique_path = '/api/v1/unique-test-' . uniqid();

                Route::query()->create([
                    'method' => 'GET',
                    'path' => $unique_path,
                ]);

                $this->expectException(\Throwable::class);
                Route::query()->create([
                    'method' => 'GET',
                    'path' => $unique_path,
                ]);
            } finally {
                Db::rollBack();
            }
        });
    }

    public function test_route_group_roles_inverse_relation(): void
    {
        $this->runInCoroutine(function (): void {
            Db::beginTransaction();
            try {
                $role = Role::query()->where('name', 'developer')->first();
                $group = RouteGroup::query()->create([
                    'name' => 'test_inverse_' . uniqid(),
                    'label' => '反向关联测试',
                ]);

                $role->routeGroups()->attach($group->id);

                // 从 RouteGroup 查询关联的 Role
                $group->load('roles');
                $this->assertCount(1, $group->roles);
                $this->assertSame($role->id, $group->roles->first()->id);
            } finally {
                Db::rollBack();
            }
        });
    }

    public function test_routes_synced_from_command(): void
    {
        $this->runInCoroutine(function (): void {
            // route:sync 已执行，验证 routes 表有数据
            $count = Route::query()->count();
            $this->assertGreaterThan(0, $count);

            // 验证已知路由存在
            $route = Route::query()
                ->where('method', 'GET')
                ->where('path', '/api/v1/users')
                ->first();
            $this->assertNotNull($route);
            $this->assertNotNull($route->name);
        });
    }
}
