<?php

namespace Tests\Feature;

use App\Livewire\Files\Index as Files;
use App\Livewire\Servers\Index as Servers;
use App\Livewire\Services\Index as Services;
use App\Models\Server;
use App\Models\User;
use App\Services\FleetService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Mockery;
use Tests\TestCase;

/**
 * RBAC view-level HIDING: operators/viewers must not SEE the controls they would be
 * 403'd from (the server-side abort_unless guards are proven in the Rbac*GateTest
 * suites). This only asserts the `@can(...)` wrappers render/omit the button markup —
 * a light UX check across a few representative surfaces, one per ability family:
 *  - Servers\Index  add-server trigger  → manage-fleet (admin sees, viewer does not);
 *  - Services\Index start/stop/restart  → operate      (operator sees, viewer does not);
 *  - the sidebar WireGuard nav item     → manage-network(admin sees, viewer does not).
 * Every SSH read is mocked — no real host is ever touched.
 */
class RbacUiHidingTest extends TestCase
{
    use RefreshDatabase;

    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }

    private function admin(): User
    {
        return User::factory()->create(['must_change_password' => false]);
    }

    private function operator(): User
    {
        return User::factory()->operator()->create(['must_change_password' => false]);
    }

    private function viewer(): User
    {
        return User::factory()->viewer()->create(['must_change_password' => false]);
    }

    /** An online server with a credential, selected as the active fleet server. */
    private function activeServer(): Server
    {
        $server = Server::create(['name' => 'box', 'ip' => '10.0.0.2', 'ssh_port' => 22, 'status' => 'online']);
        $server->credential()->create(['username' => 'root', 'auth_type' => 'password', 'secret' => 'x']);
        session(['active_server_id' => $server->id]);

        return $server;
    }

    // ── manage-fleet: Servers\Index add-server trigger ──────────────────────────

    public function test_admin_sees_the_add_server_trigger(): void
    {
        $this->actingAs($this->admin());

        Livewire::test(Servers::class)
            ->assertOk()
            ->assertSee(__('servers.add_server')); // "Server hinzufügen"
    }

    public function test_viewer_does_not_see_the_add_server_trigger(): void
    {
        $this->actingAs($this->viewer());

        Livewire::test(Servers::class)
            ->assertOk()                              // the fleet list still renders
            ->assertDontSee(__('servers.add_server')); // but the create control is gone
    }

    // ── operate: Services\Index start/stop/restart action buttons ───────────────

    /** Stubs the systemd read so the list renders with one service row. */
    private function stubServices(): void
    {
        $fleet = Mockery::mock(FleetService::class);
        $fleet->shouldReceive('systemd')->andReturn([
            'services' => [['name' => 'nginx.service', 'status' => 'offline', 'sub' => 'dead', 'enabled' => true, 'desc' => 'web']],
            'journal' => [],
            'cursor' => null,
        ]);
        app()->instance(FleetService::class, $fleet);
    }

    public function test_operator_sees_a_service_action_control(): void
    {
        $this->actingAs($this->operator());
        $this->activeServer();
        $this->stubServices();

        Livewire::test(Services::class)
            ->call('load')
            ->assertOk()
            ->assertSee(__('services.restart')); // "Neustart" — a per-row action button
    }

    public function test_viewer_does_not_see_a_service_action_control(): void
    {
        $this->actingAs($this->viewer());
        $this->activeServer();
        $this->stubServices();

        Livewire::test(Services::class)
            ->call('load')
            ->assertOk()
            ->assertSee('nginx.service')           // the list itself is visible
            ->assertDontSee(__('services.restart')); // but the action buttons are gone
    }

    // ── operate: Files\Index content controls (download / edit / delete) ────────
    // The listing (browse) is open to a viewer; the content-read + write controls are not.

    /** Stubs the SFTP directory read so the browser renders with one file row. */
    private function stubFiles(): void
    {
        $fleet = Mockery::mock(FleetService::class);
        $fleet->shouldReceive('files')->andReturn([
            ['name' => 'secrets.env', 'type' => 'file', 'size' => 12, 'perms' => '-rw-------', 'owner' => 'root', 'modified' => 'now'],
        ]);
        app()->instance(FleetService::class, $fleet);
    }

    public function test_operator_sees_the_file_content_controls(): void
    {
        $this->actingAs($this->operator());
        $this->activeServer();
        $this->stubFiles();

        Livewire::test(Files::class)
            ->call('load')
            ->assertOk()
            ->assertSee(__('files.download')) // download button present
            ->assertSee(__('common.edit'));   // edit trigger present
    }

    public function test_viewer_does_not_see_the_file_content_controls(): void
    {
        $this->actingAs($this->viewer());
        $this->activeServer();
        $this->stubFiles();

        Livewire::test(Files::class)
            ->call('load')
            ->assertOk()
            ->assertSee('secrets.env')            // the listing is visible (browse stays open)
            ->assertDontSee(__('files.download')) // but no download button
            ->assertDontSee(__('common.edit'))    // no edit trigger
            ->assertDontSee(__('common.delete')); // no delete trigger
    }

    // ── manage-network: sidebar WireGuard nav item ──────────────────────────────

    public function test_admin_sees_the_wireguard_nav(): void
    {
        $this->actingAs($this->admin());

        $this->get('/servers')
            ->assertOk()
            ->assertSee(__('shell.nav_wireguard')); // "WireGuard" nav item present
    }

    public function test_viewer_does_not_see_the_wireguard_nav(): void
    {
        $this->actingAs($this->viewer());

        $this->get('/servers')
            ->assertOk()
            ->assertDontSee(__('shell.nav_wireguard')); // hidden for a viewer
    }
}
