<?php

namespace Tests\Feature;

use App\Exceptions\DockerNotInstalled;
use App\Livewire\Servers\ServerDocker;
use App\Models\Server;
use App\Models\User;
use App\Services\DockerService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Mockery;
use Tests\TestCase;

/**
 * Per-server container management (the "Docker" tab of the server-details page). Viewing is open to
 * any role; actions require `operate`. The host-targeted sidebar page is covered by DockerComponentTest.
 */
class ServerDockerTest extends TestCase
{
    use RefreshDatabase;

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

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

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

    private function server(): 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']);

        // Re-load with the withExists('credential') attribute the component reads.
        return Server::withExists('credential')->find($server->id);
    }

    private function stubDocker(array $containers = []): DockerService
    {
        $docker = Mockery::mock(DockerService::class);
        $docker->shouldReceive('containers')->andReturn($containers);
        app()->instance(DockerService::class, $docker);

        return $docker;
    }

    public function test_viewer_can_browse_the_server_container_list(): void
    {
        $this->actingAs($this->viewer());
        $server = $this->server();
        $this->stubDocker([['id' => 'abc', 'name' => 'web', 'image' => 'nginx', 'state' => 'running', 'status' => 'Up', 'ports' => '80/tcp']]);

        Livewire::test(ServerDocker::class, ['server' => $server])
            ->call('load')
            ->assertOk()
            ->assertSet('connected', true)
            ->assertSee('web');
    }

    public function test_viewer_cannot_run_a_server_container_action(): void
    {
        $this->actingAs($this->viewer());
        $server = $this->server();
        $docker = $this->stubDocker();
        $docker->shouldReceive('containerAction')->never();

        Livewire::test(ServerDocker::class, ['server' => $server])
            ->call('action', 'web', 'stop')
            ->assertForbidden();
    }

    public function test_operator_can_run_a_server_container_action_and_it_is_audited(): void
    {
        $this->actingAs($this->operator());
        $server = $this->server();
        $docker = $this->stubDocker();
        $docker->shouldReceive('containerAction')->once()
            ->with(Mockery::type(Server::class), 'web', 'restart')
            ->andReturn(['ok' => true, 'output' => 'web']);

        Livewire::test(ServerDocker::class, ['server' => $server])
            ->call('action', 'web', 'restart')
            ->assertOk();

        $this->assertDatabaseHas('audit_events', ['action' => 'docker.action', 'server_id' => $server->id]);
    }

    public function test_shows_a_clean_not_installed_state(): void
    {
        $this->actingAs($this->viewer());
        $server = $this->server();
        $docker = Mockery::mock(DockerService::class);
        $docker->shouldReceive('containers')->once()->andThrow(new DockerNotInstalled('sh: 1: docker: not found'));
        app()->instance(DockerService::class, $docker);

        Livewire::test(ServerDocker::class, ['server' => $server])
            ->call('load')
            ->assertOk()
            ->assertSet('notInstalled', true)
            ->assertSee(__('docker.not_installed_title'));
    }

    public function test_viewer_cannot_open_server_logs(): void
    {
        $this->actingAs($this->viewer());
        $server = $this->server();
        $this->stubDocker();

        Livewire::test(ServerDocker::class, ['server' => $server])
            ->call('viewLogs', 'web')
            ->assertForbidden();
    }
}
