<?php

use App\Models\AdminUser;
use App\Models\AgentNode;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    ensureAdminActionCatalogSeeded();
    $this->artisan('lottery:admin-auth-sync')->assertExitCode(0);
});

function agentRootNodeId(int $siteId): int
{
    return (int) DB::table('agent_nodes')
        ->where('admin_site_id', $siteId)
        ->where('depth', 0)
        ->value('id');
}

function grantAgentOperatorRole(AdminUser $admin, AgentNode $agent, bool $manage = true): void
{
    $now = now();
    $roleId = DB::table('admin_roles')->insertGetId([
        'slug' => 'agent_ops_'.$admin->id,
        'code' => 'agent_ops_'.$admin->id,
        'name' => 'Agent Ops',
        'description' => null,
        'status' => 1,
        'is_system' => false,
        'sort_order' => 0,
        'created_at' => $now,
        'updated_at' => $now,
    ]);

    $codes = $manage
        ? [
            'agent.node.view',
            'agent.node.manage',
            'agent.role.view',
            'agent.role.manage',
            'agent.user.view',
            'agent.user.manage',
        ]
        : ['agent.node.view'];

    $actionIds = DB::table('admin_menu_actions')
        ->whereIn('permission_code', $codes)
        ->pluck('id');

    foreach ($actionIds as $actionId) {
        DB::table('admin_role_menu_actions')->insert([
            'role_id' => $roleId,
            'menu_action_id' => (int) $actionId,
        ]);
    }

    // Only bind to agent node, not site roles
    // This ensures the user is treated as an agent account
    DB::table('admin_user_agents')->insert([
        'admin_user_id' => $admin->id,
        'agent_node_id' => (int) $agent->id,
        'is_primary' => true,
        'granted_at' => $now,
    ]);

    DB::table('admin_user_agent_roles')->insert([
        'admin_user_id' => $admin->id,
        'agent_node_id' => (int) $agent->id,
        'role_id' => $roleId,
        'granted_at' => $now,
    ]);
}

test('each admin site has exactly one root agent node after migration', function (): void {
    $siteIds = DB::table('admin_sites')->pluck('id');
    foreach ($siteIds as $siteId) {
        expect(
            DB::table('agent_nodes')->where('admin_site_id', (int) $siteId)->where('depth', 0)->count()
        )->toBe(1);
    }
});

test('super admin can load full agent tree', function (): void {
    $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
    $admin = AdminUser::query()->create([
        'username' => 'agent_super',
        'name' => 'Super',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantSuperAdminRole($admin);
    $token = $admin->createToken('test', ['*'], now()->addDay())->plainTextToken;

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->getJson('/api/v1/admin/agent-nodes/tree?admin_site_id='.$siteId)
        ->assertOk()
        ->assertJsonPath('code', 0)
        ->assertJsonStructure(['data' => ['admin_site_id', 'tree']]);
});

test('agent operator only sees own subtree', function (): void {
    $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
    $rootId = agentRootNodeId($siteId);

    $service = app(\App\Services\Agent\AgentNodeService::class);
    $super = AdminUser::query()->create([
        'username' => 'bootstrap',
        'name' => 'Bootstrap',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantSuperAdminRole($super);

    $nodeA = $service->createChild($super, [
        'parent_id' => $rootId,
        'code' => 'branch-a',
        'name' => 'Branch A',
        'status' => 1,
    ]);
    $service->createChild($super, [
        'parent_id' => $rootId,
        'code' => 'branch-b',
        'name' => 'Branch B',
        'status' => 1,
    ]);

    $operator = AdminUser::query()->create([
        'username' => 'agent_a_ops',
        'name' => 'A Ops',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantAgentOperatorRole($operator, $nodeA);

    $token = $operator->createToken('test', ['*'], now()->addDay())->plainTextToken;

    $response = $this->withHeader('Authorization', 'Bearer '.$token)
        ->getJson('/api/v1/admin/agent-nodes/tree');

    $response->assertOk();
    $tree = $response->json('data.tree');
    expect($tree)->toBeArray()->and(count($tree))->toBe(1);
    expect($tree[0]['code'])->toBe('branch-a');
    expect(collect($tree)->pluck('code'))->not->toContain('branch-b');
});

test('agent operator can create child under own node but not under sibling', function (): void {
    $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
    $rootId = agentRootNodeId($siteId);
    $service = app(\App\Services\Agent\AgentNodeService::class);
    $super = AdminUser::query()->create([
        'username' => 'bootstrap2',
        'name' => 'Bootstrap',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantSuperAdminRole($super);

    $nodeA = $service->createChild($super, agentChildPayload([
        'parent_id' => $rootId,
        'code' => 'branch-a2',
        'name' => 'Branch A',
    ]));
    $nodeB = $service->createChild($super, agentChildPayload([
        'parent_id' => $rootId,
        'code' => 'branch-b2',
        'name' => 'Branch B',
    ]));

    $operator = AdminUser::query()->create([
        'username' => 'agent_a2_ops',
        'name' => 'A Ops',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantAgentOperatorRole($operator, $nodeA);
    $token = $operator->createToken('test', ['*'], now()->addDay())->plainTextToken;

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->postJson('/api/v1/admin/agent-nodes', [
            'parent_id' => $nodeA->id,
            'code' => 'a-child',
            'name' => 'A Child',
            'password' => agentNodeTestPassword(),
        ])
        ->assertOk()
        ->assertJsonPath('data.code', 'a-child');

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->postJson('/api/v1/admin/agent-nodes', [
            'parent_id' => $nodeB->id,
            'code' => 'hack-child',
            'name' => 'Hack',
            'password' => agentNodeTestPassword(),
        ])
        ->assertForbidden();
});

test('agent operator cannot create role or admin user under descendant node', function (): void {
    $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
    $rootId = agentRootNodeId($siteId);
    $service = app(\App\Services\Agent\AgentNodeService::class);
    $super = AdminUser::query()->create([
        'username' => 'bootstrap4',
        'name' => 'Bootstrap',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantSuperAdminRole($super);

    $nodeA = $service->createChild($super, agentChildPayload([
        'parent_id' => $rootId,
        'code' => 'branch-a3',
        'name' => 'Branch A3',
    ]));
    $child = $service->createChild($super, agentChildPayload([
        'parent_id' => $nodeA->id,
        'code' => 'branch-a3-child',
        'name' => 'Branch A3 Child',
    ]));

    $operator = AdminUser::query()->create([
        'username' => 'agent_a3_ops',
        'name' => 'A3 Ops',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantAgentOperatorRole($operator, $nodeA);
    $token = $operator->createToken('test', ['*'], now()->addDay())->plainTextToken;

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->postJson('/api/v1/admin/agent-nodes/'.$child->id.'/roles', [
            'slug' => 'child_role',
            'name' => 'Child Role',
            'permission_slugs' => ['prd.agent.view'],
        ])
        ->assertForbidden();

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->postJson('/api/v1/admin/agent-nodes/'.$child->id.'/admin-users', [
            'username' => 'child_admin',
            'nickname' => 'Child Admin',
            'password' => 'Secret123!',
            'role_ids' => [],
        ])
        ->assertForbidden();
});

test('auth me returns agent context for bound operator', function (): void {
    $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
    $rootId = agentRootNodeId($siteId);
    $service = app(\App\Services\Agent\AgentNodeService::class);
    $super = AdminUser::query()->create([
        'username' => 'bootstrap3',
        'name' => 'Bootstrap',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantSuperAdminRole($super);

    $node = $service->createChild($super, [
        'parent_id' => $rootId,
        'code' => 'me-branch',
        'name' => 'Me Branch',
    ]);

    $operator = AdminUser::query()->create([
        'username' => 'me_agent_ops',
        'name' => 'Ops',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantAgentOperatorRole($operator, $node);
    $token = $operator->createToken('test', ['*'], now()->addDay())->plainTextToken;

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->getJson('/api/v1/admin/auth/me')
        ->assertOk()
        ->assertJsonPath('data.admin.agent.code', 'me-branch')
        ->assertJsonPath('data.admin.is_super_admin', false);
});

test('agent node deletion requires leaf node without bindings', function (): void {
    $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
    $rootId = agentRootNodeId($siteId);
    $service = app(\App\Services\Agent\AgentNodeService::class);

    $super = AdminUser::query()->create([
        'username' => 'delete_agent_super',
        'name' => 'Delete Super',
        'email' => null,
        'password' => Hash::make('secret-strong'),
        'status' => 0,
    ]);
    grantSuperAdminRole($super);
    $token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken;

    $nodeWithChild = $service->createChild($super, [
        'parent_id' => $rootId,
        'code' => 'delete-parent',
        'name' => 'Delete Parent',
    ]);
    $service->createChild($super, [
        'parent_id' => $nodeWithChild->id,
        'code' => 'delete-child',
        'name' => 'Delete Child',
    ]);

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->deleteJson('/api/v1/admin/agent-nodes/'.$rootId)
        ->assertStatus(422)
        ->assertJsonPath('msg', __('admin.agent_root_delete_denied'));

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->deleteJson('/api/v1/admin/agent-nodes/'.$nodeWithChild->id)
        ->assertStatus(422)
        ->assertJsonPath('msg', __('admin.agent_node_has_children_cannot_delete'));

    $leaf = $service->createChild($super, [
        'parent_id' => $rootId,
        'code' => 'delete-leaf',
        'name' => 'Delete Leaf',
    ]);

    $this->withHeader('Authorization', 'Bearer '.$token)
        ->deleteJson('/api/v1/admin/agent-nodes/'.$leaf->id)
        ->assertOk()
        ->assertJsonPath('code', 0);

    expect(AgentNode::query()->find($leaf->id))->toBeNull();
});
