<?php

use App\Livewire\Admin\ConfirmRemoveHost;
use App\Livewire\Admin\HostCreate;
use App\Livewire\Admin\HostDetail;
use App\Livewire\Admin\Hosts;
use App\Models\Host;
use App\Models\ProvisioningRun;
use App\Models\User;
use App\Provisioning\Jobs\AdvanceRunJob;
use App\Provisioning\Jobs\PurgeHost;
use App\Provisioning\Jobs\RemoveWireguardPeer;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;

it('gates the add-host page', function () {
    $this->get(route('admin.hosts.create'))->assertRedirect(route('admin.login'));
    $this->actingAs(User::factory()->create(['is_admin' => false]))->get(route('admin.hosts.create'))->assertForbidden();
    $this->actingAs(admin(), 'operator')->get(route('admin.hosts.create'))->assertOk()->assertSee(__('hosts.create_title'));
});

it('gates the host detail page', function () {
    $host = Host::factory()->create();
    $this->get(route('admin.hosts.show', $host))->assertRedirect(route('admin.login'));
    $this->actingAs(User::factory()->create(['is_admin' => false]))->get(route('admin.hosts.show', $host))->assertForbidden();
    $this->actingAs(admin(), 'operator')->get(route('admin.hosts.show', $host))->assertOk()->assertSee($host->name);
});

it('lists real hosts for admins', function () {
    Host::factory()->active()->create(['name' => 'pve-zzz-1']);

    $this->actingAs(admin(), 'operator')->get(route('admin.hosts'))->assertOk()->assertSee('pve-zzz-1');
});

it('creates a host and starts onboarding with an encrypted password', function () {
    Queue::fake();
    \App\Models\Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);

    Livewire::actingAs(admin(), 'operator')
        ->test(HostCreate::class)
        ->set('name', 'pve-fsn-7')
        ->set('datacenter', 'fsn')
        ->set('public_ip', '203.0.113.7')
        ->set('root_password', 'supersecret')
        ->call('save')
        ->assertRedirect();

    $host = Host::query()->where('name', 'pve-fsn-7')->first();
    expect($host)->not->toBeNull()->and($host->status)->toBe('pending');

    $run = ProvisioningRun::query()->where('subject_id', $host->id)->first();
    expect($run)->not->toBeNull()
        ->and($run->pipeline)->toBe('host')
        ->and(Crypt::decryptString($run->context('root_password')))->toBe('supersecret');

    Queue::assertPushed(AdvanceRunJob::class);
});

it('rejects a duplicate public ip', function () {
    \App\Models\Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);
    Host::factory()->create(['public_ip' => '203.0.113.99']);

    Livewire::actingAs(admin(), 'operator')
        ->test(HostCreate::class)
        ->set('name', 'pve-dup')
        ->set('datacenter', 'fsn')
        ->set('public_ip', '203.0.113.99')
        ->set('root_password', 'supersecret')
        ->call('save')
        ->assertHasErrors(['public_ip']);
});

it('validates the add-host form', function () {
    Livewire::actingAs(admin(), 'operator')
        ->test(HostCreate::class)
        ->set('name', '')
        ->set('public_ip', 'not-an-ip')
        ->set('root_password', 'short')
        ->call('save')
        ->assertHasErrors(['name', 'public_ip', 'root_password']);
});

it('retries a failed run from the detail page', function () {
    Queue::fake();
    $host = Host::factory()->create(['status' => 'error']);
    $run = ProvisioningRun::factory()->forHost($host)->create([
        'status' => 'failed', 'current_step' => 3, 'error' => 'boom',
        'started_at' => now()->subHour(),
    ]);

    Livewire::actingAs(admin(), 'operator')->test(HostDetail::class, ['host' => $host])->call('retry');

    $run->refresh();
    expect($run->status)->toBe('running')
        ->and($run->error)->toBeNull()
        ->and($run->started_at->greaterThan(now()->subMinute()))->toBeTrue()
        ->and($host->fresh()->status)->toBe('onboarding');
    Queue::assertPushed(AdvanceRunJob::class);
});

it('deactivates the host and queues an async purge', function () {
    Queue::fake();
    $host = Host::factory()->active()->create(['wg_pubkey' => 'HOSTPUB=']);
    ProvisioningRun::factory()->forHost($host)->create();

    Livewire::actingAs(admin(), 'operator')
        ->test(ConfirmRemoveHost::class, ['uuid' => $host->uuid])
        ->call('remove')
        ->assertRedirect(route('admin.hosts'));

    expect($host->fresh()->status)->toBe('disabled');
    Queue::assertPushed(PurgeHost::class, fn ($job) => $job->uuid === $host->uuid);
});

it('purges a host: deletes its runs and record and queues peer removal', function () {
    Queue::fake([RemoveWireguardPeer::class]);
    $host = Host::factory()->active()->create(['wg_pubkey' => 'HOSTPUB=']);
    ProvisioningRun::factory()->forHost($host)->create();

    (new PurgeHost($host->uuid))->handle();

    expect(Host::query()->find($host->id))->toBeNull();
    Queue::assertPushed(RemoveWireguardPeer::class, fn ($job) => $job->publicKey === 'HOSTPUB=');
});

it('renders the live stepper for a running host', function () {
    $host = Host::factory()->create(['status' => 'onboarding']);
    ProvisioningRun::factory()->forHost($host)->create(['status' => 'running', 'current_step' => 2]);

    $this->actingAs(admin(), 'operator')->get(route('admin.hosts.show', $host))
        ->assertOk()
        ->assertSee(__('hosts.step.prepare_base_system'))
        ->assertSee(__('hosts.progress'));
});

it('filters the host list by search and datacenter', function () {
    \App\Models\Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);
    \App\Models\Datacenter::query()->firstOrCreate(['code' => 'hel'], ['name' => 'Helsinki']);
    Host::factory()->create(['name' => 'pve-fsn-9', 'datacenter' => 'fsn', 'public_ip' => '203.0.113.9']);
    Host::factory()->create(['name' => 'pve-hel-9', 'datacenter' => 'hel', 'public_ip' => '203.0.113.19']);

    Livewire::actingAs(admin(), 'operator')->test(Hosts::class)
        ->set('search', 'fsn-9')
        ->assertSee('pve-fsn-9')
        ->assertDontSee('pve-hel-9')
        ->set('search', '')
        ->set('datacenter', 'hel')
        ->assertSee('pve-hel-9')
        ->assertDontSee('pve-fsn-9');
});

it('edits host reserve and toggles maintenance', function () {
    $host = Host::factory()->active()->create(['reserve_pct' => 15]);

    Livewire::actingAs(admin(), 'operator')->test(HostDetail::class, ['host' => $host])
        ->call('saveReserve', 25)
        ->call('toggleMaintenance');

    $host->refresh();
    expect($host->reserve_pct)->toBe(25)->and($host->status)->toBe('disabled');

    Livewire::actingAs(admin(), 'operator')->test(HostDetail::class, ['host' => $host])->call('toggleMaintenance');
    expect($host->fresh()->status)->toBe('active');
});

it('clamps host reserve to a sane range', function () {
    $host = Host::factory()->active()->create(['reserve_pct' => 15]);

    Livewire::actingAs(admin(), 'operator')->test(HostDetail::class, ['host' => $host])->call('saveReserve', 500);
    expect($host->fresh()->reserve_pct)->toBe(90);
});

it('reports host heartbeat health from last_seen_at', function () {
    $online = Host::factory()->create(['last_seen_at' => now()->subMinutes(2)]);
    $stale = Host::factory()->create(['last_seen_at' => now()->subMinutes(20)]);
    $offline = Host::factory()->create(['last_seen_at' => now()->subHours(3)]);
    $never = Host::factory()->create(['last_seen_at' => null]);

    expect($online->healthState())->toBe('online')
        ->and($stale->healthState())->toBe('stale')
        ->and($offline->healthState())->toBe('offline')
        ->and($never->healthState())->toBe('offline');
});
