<?php

namespace Tests\Feature;

use App\Livewire\Services\Index;
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;

/**
 * The journal is "live" via an incremental poll: each tick appends entries AFTER the
 * stored cursor (no gaps), advances the cursor, and caps the retained rows.
 */
class ServicesJournalPollTest extends TestCase
{
    use RefreshDatabase;

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

    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;
    }

    public function test_poll_appends_new_entries_and_advances_the_cursor(): void
    {
        $this->actingAs(User::factory()->create());
        $this->activeServer();

        $fleet = Mockery::mock(FleetService::class);
        $fleet->shouldReceive('journalSince')
            ->once()
            ->with(Mockery::type(Server::class), 'cur-0', 200)
            ->andReturn([
                'journal' => [['time' => '10:00:05', 'unit' => 'nginx', 'level' => 'info', 'text' => 'new line']],
                'cursor' => 'cur-1',
            ]);
        app()->instance(FleetService::class, $fleet);

        Livewire::test(Index::class)
            ->set('ready', true)
            ->set('connected', true)
            ->set('journalCursor', 'cur-0')
            ->set('journal', [['time' => '10:00:01', 'unit' => 'sshd', 'level' => 'info', 'text' => 'old line']])
            ->call('pollJournal')
            ->assertSet('journalCursor', 'cur-1')
            ->assertSet('journal', [
                ['time' => '10:00:01', 'unit' => 'sshd', 'level' => 'info', 'text' => 'old line'],
                ['time' => '10:00:05', 'unit' => 'nginx', 'level' => 'info', 'text' => 'new line'],
            ]);
    }

    public function test_poll_caps_retained_rows_at_the_ceiling(): void
    {
        $this->actingAs(User::factory()->create());
        $this->activeServer();

        $fresh = array_map(
            fn (int $i) => ['time' => '10:00:'.$i, 'unit' => 'u', 'level' => 'info', 'text' => 'n'.$i],
            range(1, 350),
        );

        $fleet = Mockery::mock(FleetService::class);
        $fleet->shouldReceive('journalSince')->once()->andReturn(['journal' => $fresh, 'cursor' => 'c']);
        app()->instance(FleetService::class, $fleet);

        $component = Livewire::test(Index::class)
            ->set('ready', true)
            ->set('connected', true)
            ->set('journal', [['time' => '09:59:59', 'unit' => 'old', 'level' => 'info', 'text' => 'oldest']])
            ->call('pollJournal');

        $journal = $component->get('journal');
        $this->assertCount(200, $journal);
        // The oldest row is evicted; the newest survives.
        $this->assertSame('n350', end($journal)['text']);
        $this->assertNotSame('oldest', $journal[0]['text']);
    }

    public function test_poll_is_a_noop_until_connected(): void
    {
        $this->actingAs(User::factory()->create());
        $this->activeServer();

        $fleet = Mockery::mock(FleetService::class);
        $fleet->shouldNotReceive('journalSince');
        app()->instance(FleetService::class, $fleet);

        Livewire::test(Index::class)
            ->set('ready', true)
            ->set('connected', false)
            ->set('journal', [['time' => 't', 'unit' => 'u', 'level' => 'info', 'text' => 'unchanged']])
            ->call('pollJournal')
            ->assertSet('journal', [['time' => 't', 'unit' => 'u', 'level' => 'info', 'text' => 'unchanged']]);
    }
}
