<?php

use App\Actions\StartHostOnboarding;
use App\Models\ProvisioningRun;
use App\Models\RunResource;
use App\Provisioning\RunRunner;
use App\Provisioning\Steps\Host\ConfigureWireguard;
use App\Services\Ssh\CommandResult;
use Illuminate\Support\Facades\Queue;

it('drives a fresh host all the way to active (mocked)', function () {
    Queue::fake(); // drive the runner manually, one step per advance
    $s = fakeServices();
    $s['shell']->script('wg pubkey', CommandResult::success('HOSTPUBKEY0000='));
    $s['shell']->script('uname -r', CommandResult::success('6.8.12-4-pve'));
    $s['shell']->script('pveum user token add', CommandResult::success(
        json_encode(['full-tokenid' => 'automation@pve!clupilot', 'value' => 'tok-secret-123'])
    ));

    $host = app(StartHostOnboarding::class)->run([
        'name' => 'pve-fsn-9',
        'datacenter' => 'fsn',
        'public_ip' => '203.0.113.9',
        'root_password' => 'rootpw',
    ]);

    $run = ProvisioningRun::query()->where('subject_id', $host->id)->firstOrFail();
    $runner = app(RunRunner::class);

    for ($i = 0; $i < 40; $i++) {
        $run->refresh();
        if (in_array($run->status, ['completed', 'failed'], true)) {
            break;
        }
        if ($run->next_attempt_at?->isFuture()) {
            $run->update(['next_attempt_at' => now()]); // simulate the scheduler waking a due run
        }
        $runner->advance($run);
    }

    $run->refresh();
    $host->refresh();

    expect($run->status)->toBe('completed')
        ->and($run->error)->toBeNull()
        ->and($host->status)->toBe('active')
        ->and($host->wg_ip)->not->toBeNull()
        ->and($host->total_gb)->toBe(1024)
        ->and($host->cpu_cores)->toBe(16)
        ->and($host->api_token_ref)->toContain('automation@pve')
        // external resources created exactly once
        ->and(RunResource::where('run_id', $run->id)->where('kind', 'wg_peer')->count())->toBe(1)
        ->and(RunResource::where('run_id', $run->id)->where('kind', 'pve_token')->count())->toBe(1)
        ->and(RunResource::where('run_id', $run->id)->where('kind', 'host_firewall')->count())->toBe(1)
        // one-time root password scrubbed from state
        ->and($run->context('root_password'))->toBeNull();

    // The firewall step ran last, over the tunnel, and left its rules + the
    // manual emergency-release script on the host.
    expect($s['shell']->files())->toHaveKey('/etc/nftables.conf')
        ->and($s['shell']->files())->toHaveKey('/usr/local/sbin/clupilot-emergency-open-firewall.sh');
});

it('does not duplicate external resources when a step re-runs after a crash', function () {
    Queue::fake();
    $s = fakeServices();
    $s['shell']->script('wg pubkey', CommandResult::success('HOSTPUBKEY0000='));
    $s['shell']->script('uname -r', CommandResult::success('6.8.12-4-pve'));
    $s['shell']->script('pveum user token add', CommandResult::success(
        json_encode(['full-tokenid' => 'automation@pve!clupilot', 'value' => 'tok-secret-123'])
    ));

    $host = app(StartHostOnboarding::class)->run([
        'name' => 'pve-fsn-10',
        'datacenter' => 'fsn',
        'public_ip' => '203.0.113.10',
        'root_password' => 'rootpw',
    ]);
    $run = ProvisioningRun::query()->where('subject_id', $host->id)->firstOrFail();
    $runner = app(RunRunner::class);

    // Advance a few steps, then simulate a crash by rewinding the run to the
    // WireGuard step and replaying it — the breadcrumb must prevent duplicates.
    for ($i = 0; $i < 6; $i++) {
        $run->refresh();
        if ($run->next_attempt_at?->isFuture()) {
            $run->update(['next_attempt_at' => now()]);
        }
        $runner->advance($run);
    }
    $run->refresh();
    $wgIndex = array_search(
        ConfigureWireguard::class,
        config('provisioning.pipelines.host'),
        true,
    );
    $run->update(['current_step' => $wgIndex, 'status' => 'running']);
    $runner->advance($run->fresh());

    expect(RunResource::where('run_id', $run->id)->where('kind', 'wg_peer')->count())->toBe(1);
});
