<?php

use App\Livewire\Admin\InstanceAdminAccess;
use App\Models\Instance;
use App\Provisioning\Jobs\IssueInstanceAdminAccess;
use App\Services\Proxmox\FakeProxmoxClient;
use App\Services\Proxmox\ProxmoxClient;
use App\Services\Wireguard\ConfigHandoff;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Queue;

function adminAccessInstance(): Instance
{
    return Instance::factory()->create([
        'host_id' => App\Models\Host::factory()->active()->create()->id,
        'vmid' => 1042,
        'nc_admin_ref' => 'admin',
        'subdomain' => 'kanzlei-berger',
        'status' => 'active',
    ]);
}

it('is offered only to operators who may take over an installation', function () {
    $instance = adminAccessInstance();

    foreach (['Support', 'Billing', 'Read-only', 'Developer'] as $role) {
        Livewire\Livewire::actingAs(operator($role), 'operator')
            ->test(InstanceAdminAccess::class, ['uuid' => $instance->uuid])
            ->assertForbidden();
    }

    Livewire\Livewire::actingAs(operator('Owner'), 'operator')
        ->test(InstanceAdminAccess::class, ['uuid' => $instance->uuid])
        ->assertOk();
});

it('asks for the operator password before touching the instance', function () {
    Queue::fake();
    $instance = adminAccessInstance();
    $owner = operator('Owner');
    $owner->forceFill(['password' => Hash::make('geheim-1234')])->save();

    $modal = Livewire\Livewire::actingAs($owner, 'operator')
        ->test(InstanceAdminAccess::class, ['uuid' => $instance->uuid])
        ->set('password', 'falsch')
        ->call('request')
        ->assertHasErrors('password');

    Queue::assertNothingPushed();

    $modal->set('password', 'geheim-1234')->call('request')->assertHasNoErrors();
    Queue::assertPushed(IssueInstanceAdminAccess::class);
});

it('resets our managed admin account and hands the credentials over once', function () {
    $pve = new FakeProxmoxClient;
    app()->instance(ProxmoxClient::class, $pve);
    $instance = adminAccessInstance();
    $token = 'handoff-token-for-the-test';

    (new IssueInstanceAdminAccess($instance->uuid, $token, 1))->handle($pve);

    $payload = json_decode(ConfigHandoff::get($token), true);
    expect($payload['username'])->toBe('admin')
        ->and($payload['password'])->toHaveLength(24)
        ->and($payload['url'])->toContain('kanzlei-berger');

    // The password is handed over, never written to the instance record.
    expect($instance->fresh()->getAttributes())->not->toContain($payload['password']);
});

it('reports a silent instance instead of pretending it worked', function () {
    $pve = new class extends FakeProxmoxClient
    {
        public function guestExec(string $node, int $vmid, string $command): array
        {
            return ['exitcode' => 1, 'out-data' => 'connection refused'];
        }
    };
    app()->instance(ProxmoxClient::class, $pve);
    $instance = adminAccessInstance();
    $token = 'handoff-failure';

    (new IssueInstanceAdminAccess($instance->uuid, $token, 1))->handle($pve);

    expect(json_decode(ConfigHandoff::get($token), true))->toBe(['error' => 'failed']);
});

it('refuses an instance that is no longer live', function () {
    $pve = new FakeProxmoxClient;
    app()->instance(ProxmoxClient::class, $pve);
    $instance = adminAccessInstance();
    $instance->update(['status' => 'terminated']);

    (new IssueInstanceAdminAccess($instance->uuid, 'handoff-dead', 1))->handle($pve);

    // Its VMID may belong to someone else by now.
    expect(json_decode(ConfigHandoff::get('handoff-dead'), true))->toBe(['error' => 'unavailable']);
});

it('tells the console when Proxmox is unreachable', function () {
    $pve = new class extends FakeProxmoxClient
    {
        public function guestExec(string $node, int $vmid, string $command): array
        {
            throw new RuntimeException('no route to host');
        }
    };
    app()->instance(ProxmoxClient::class, $pve);
    $instance = adminAccessInstance();

    (new IssueInstanceAdminAccess($instance->uuid, 'handoff-throw', 1))->handle($pve);

    // Otherwise the modal polls forever with a spinner.
    expect(json_decode(ConfigHandoff::get('handoff-throw'), true))->toBe(['error' => 'failed']);
});

it('shows the password once and not a second time', function () {
    $pve = new FakeProxmoxClient;
    app()->instance(ProxmoxClient::class, $pve);
    $instance = adminAccessInstance();
    $owner = operator('Owner');
    $owner->forceFill(['password' => Hash::make('geheim-1234')])->save();

    $modal = Livewire\Livewire::actingAs($owner, 'operator')
        ->test(InstanceAdminAccess::class, ['uuid' => $instance->uuid])
        ->set('password', 'geheim-1234')
        ->call('request');            // sync queue: the job runs immediately

    $token = $modal->get('token');
    $modal->assertSee(__('instances.admin_ready'));

    // "Shown once" has to mean it: a replayed request must not fetch it again.
    expect(ConfigHandoff::get($token ?? 'none'))->toBeNull()
        ->and($modal->get('token'))->toBeNull();

    $modal->call('$refresh')->assertDontSee(__('instances.admin_ready'));
});
