<?php

use App\Models\Host;
use App\Models\Instance;
use App\Models\Mailbox;
use App\Models\Order;
use App\Models\ProvisioningRun;
use App\Models\RunResource;
use App\Notifications\CloudReady;
use App\Provisioning\RunRunner;
use App\Provisioning\Steps\Customer\CloneVirtualMachine;
use App\Support\Settings;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Queue;

it('provisions a paid order all the way to active (mocked)', function () {
    Notification::fake();
    Queue::fake(); // drive the runner manually, one step per advance
    $s = fakeServices();
    $s['pve']->guestScript('status.php', 0, 'ok');
    $s['pve']->guestScript('hostname -I', 0, '10.20.0.9');
    $s['pve']->guestScript('occ status', 0, '{"installed":true,"maintenance":false}'); // deploy health + acceptance probe
    // Fake defaults: tasks stopped/OK, guest agent up, cert reachable.

    // ConfigureInstanceMail steht jetzt in dieser Pipeline. Mailversand ist
    // eingerichtet, damit DIESER Lauf den Schreib-Pfad beweist (siehe
    // Zusicherung weiter unten, dass der Gast die Werte tatsächlich bekam).
    // Der Fall OHNE Einrichtung — ein bezahlter Kunde bekommt seine Cloud
    // trotzdem — hat einen eigenen Test weiter unten, weil er das genaue
    // Gegenteil dieser Fixture braucht.
    Settings::set('mail.host', 'mail.clupilot.cloud');
    Settings::set('mail.port', 587);
    // Ausgefüllt, nicht angelegt: die Zeile bringt eine eigene Wanderung mit
    // — inaktiv und ohne Zugangsdaten, damit sie in der Konsole erscheint und
    // dort ausgefüllt werden kann.
    Mailbox::findByKey('instance-relay')->update([
        'address' => 'noreply@clupilot.cloud',
        'username' => 'noreply@clupilot.cloud', 'password' => 'geheim',
        'active' => true, 'authenticates' => true,
    ]);

    Host::factory()->active()->create(['datacenter' => 'fsn', 'node' => 'pve', 'total_gb' => 1000]);
    $order = Order::factory()->withSubscription()->create(['status' => 'paid', 'plan' => 'start', 'datacenter' => 'fsn']);
    $run = ProvisioningRun::factory()->create([
        'subject_type' => Order::class, 'subject_id' => $order->id, 'pipeline' => 'customer', 'context' => [],
    ]);

    $runner = app(RunRunner::class);
    for ($i = 0; $i < 60; $i++) {
        $run->refresh();
        if (in_array($run->status, ['completed', 'failed'], true)) {
            break;
        }
        if ($run->next_attempt_at?->isFuture()) {
            $run->update(['next_attempt_at' => now()]);
        }
        $runner->advance($run);
    }

    $run->refresh();
    $instance = Instance::query()->where('order_id', $order->id)->firstOrFail();

    expect($run->status)->toBe('completed')
        ->and($run->error)->toBeNull()
        ->and($order->fresh()->status)->toBe('active')
        ->and($instance->status)->toBe('active')
        ->and($instance->vmid)->not->toBeNull()
        ->and($instance->cert_ok)->toBeTrue()
        ->and($instance->onboardingTasks()->count())->toBeGreaterThan(0)
        // The storage allowance was DELIVERED, not merely walked past. This test
        // covers every step of the pipeline and asserted nothing about the quota one, so it
        // proved that inserting the step did not break the pipeline rather than
        // that the step does anything — and quota_applied_gb is written only after
        // the guest accepted the figure, which is the whole distinction between an
        // enforced allowance and a number in our database.
        ->and($instance->quota_applied_gb)->toBe($instance->quota_gb)
        ->and($instance->quota_applied_gb)->toBeGreaterThan(0)
        ->and($s['pve']->guestRan('config:app:set files default_quota'))->toBeTrue()
        // Mail war eingerichtet (siehe Fixture oben) — der Schreib-Pfad von
        // ConfigureInstanceMail lief also tatsächlich, nicht nur der
        // Überspringen-Pfad, den der Test weiter unten prüft.
        ->and($s['pve']->guestRan('config:system:set mail_smtphost'))->toBeTrue();

    // Every external resource created exactly once.
    foreach (['instance_id', 'vmid', 'dns_record_id', 'nc_admin', 'backup_job_id', 'monitoring_target_id'] as $kind) {
        expect(RunResource::where('run_id', $run->id)->where('kind', $kind)->count())->toBe(1);
    }

    // Secrets scrubbed from run state; the admin password moved onto the
    // instance instead — held there, not mailed, until the customer
    // acknowledges it on the dashboard.
    expect($run->context('admin_password'))->toBeNull()
        ->and($run->context('db_password'))->toBeNull()
        ->and($instance->fresh()->admin_password)->not->toBeNull()
        ->and($instance->fresh()->credentials_acknowledged_at)->toBeNull();
    Notification::assertSentOnDemand(CloudReady::class);
});

it('completes a paid order even when the mail server does not exist yet', function () {
    // Der Mailserver dieses Produkts wird gerade erst aufgesetzt — es gibt
    // ihn zum Zeitpunkt dieses Laufs noch nicht. Ein bezahlter Kunde bekommt
    // seine Cloud trotzdem: ConfigureInstanceMail ist eine Nebenfunktion in
    // einer PFLICHT-Pipeline und darf sie nicht aufhalten, genau wie
    // RegisterMonitoring die Bereitstellung nie an einer nicht erreichbaren
    // Überwachung scheitern lässt. Bewusst KEINE mail.host/mail.port- oder
    // Mailbox-Fixture hier — das ist der ganze Punkt dieses Tests.
    Notification::fake();
    Queue::fake();
    $s = fakeServices();
    $s['pve']->guestScript('status.php', 0, 'ok');
    $s['pve']->guestScript('hostname -I', 0, '10.20.0.9');
    $s['pve']->guestScript('occ status', 0, '{"installed":true,"maintenance":false}');

    Host::factory()->active()->create(['datacenter' => 'fsn', 'node' => 'pve', 'total_gb' => 1000]);
    $order = Order::factory()->withSubscription()->create(['status' => 'paid', 'plan' => 'start', 'datacenter' => 'fsn']);
    $run = ProvisioningRun::factory()->create([
        'subject_type' => Order::class, 'subject_id' => $order->id, 'pipeline' => 'customer', 'context' => [],
    ]);

    $runner = app(RunRunner::class);
    for ($i = 0; $i < 60; $i++) {
        $run->refresh();
        if (in_array($run->status, ['completed', 'failed'], true)) {
            break;
        }
        if ($run->next_attempt_at?->isFuture()) {
            $run->update(['next_attempt_at' => now()]);
        }
        $runner->advance($run);
    }

    $run->refresh();
    $instance = Instance::query()->where('order_id', $order->id)->firstOrFail();

    expect($run->status)->toBe('completed')
        ->and($run->error)->toBeNull()
        ->and($order->fresh()->status)->toBe('active')
        ->and($instance->status)->toBe('active')
        // Das Gegenstück zur Zusicherung im ersten Test: ohne Einrichtung
        // ging kein einziger Mailbefehl an den Gast, weder die Werte noch das
        // Passwort.
        ->and($s['pve']->guestRan('config:system:set mail_smtphost'))->toBeFalse()
        ->and($s['pve']->guestRan('mail_smtppassword'))->toBeFalse();
});

it('does not duplicate external resources when a step re-runs after a crash', function () {
    Notification::fake();
    Queue::fake();
    $s = fakeServices();
    $s['pve']->guestScript('status.php', 0, 'ok');
    $s['pve']->guestScript('hostname -I', 0, '10.20.0.9');
    $s['pve']->guestScript('occ status', 0, '{"installed":true,"maintenance":false}');

    Host::factory()->active()->create(['datacenter' => 'fsn', 'node' => 'pve', 'total_gb' => 1000]);
    $order = Order::factory()->withSubscription()->create(['status' => 'paid', 'plan' => 'start', 'datacenter' => 'fsn']);
    $run = ProvisioningRun::factory()->create([
        'subject_type' => Order::class, 'subject_id' => $order->id, 'pipeline' => 'customer', 'context' => [],
    ]);
    $runner = app(RunRunner::class);

    // Drive a few steps, then rewind to the clone step and replay it.
    for ($i = 0; $i < 5; $i++) {
        $run->refresh();
        if ($run->next_attempt_at?->isFuture()) {
            $run->update(['next_attempt_at' => now()]);
        }
        $runner->advance($run);
    }
    $cloneIndex = array_search(
        CloneVirtualMachine::class,
        config('provisioning.pipelines.customer'),
        true,
    );
    $run->update(['current_step' => $cloneIndex, 'status' => 'running']);
    $runner->advance($run->fresh());

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