<?php

use App\Models\Host;
use App\Models\Operator;
use App\Models\PlanFamily;
use App\Services\Secrets\SecretVault;
use App\Support\OperatingMode;
use App\Support\Readiness;
use App\Support\Readiness\Check;
use App\Support\Settings;

function provisioningCheck(string $key): ?Check
{
    return collect(Readiness::all())->firstWhere('key', $key);
}

/**
 * Am 2026-07-30 standen zwei Hosts auf `active`, ohne api_token_ref und mit
 * Adressen aus dem Dokumentationsbereich (203.0.113.0/24, RFC 5737). In der
 * Konsole waren sie von einem echten Host nicht zu unterscheiden — und
 * ReserveResources wählt aus `status = active`. Ein Kauf hätte sich einen
 * dieser Phantom-Hosts reserviert und wäre NACH der Zahlung gestorben.
 */
it('does not accept an active host without a readable token', function () {
    Host::factory()->create(['status' => 'active', 'api_token_ref' => null]);

    expect(provisioningCheck('provisioning.usable_host')->satisfied)->toBeFalse();
});

it('accepts an active host that carries a token', function () {
    Host::factory()->create(['status' => 'active', 'api_token_ref' => 'ref-1']);

    expect(provisioningCheck('provisioning.usable_host')->satisfied)->toBeTrue();
});

it('does not accept a host that carries a token but is not active', function () {
    Host::factory()->create(['status' => 'onboarding', 'api_token_ref' => 'ref-1']);

    expect(provisioningCheck('provisioning.usable_host')->satisfied)->toBeFalse();
});

it('treats the monitoring token as a warning while monitoring is not required', function () {
    // Überwachung darf eine Bereitstellung nie aufhalten — solange der
    // Betreiber sie nicht selbst zur Pflicht gemacht hat. Explizit gesetzt,
    // statt sich auf die Vorgabe dieser Installation zu verlassen.
    config()->set('provisioning.monitoring.required', false);

    expect(provisioningCheck('provisioning.monitoring_token')->severity)
        ->toBe(Check::SEVERITY_WARNING);
});

/**
 * Fix-Runde (Befund 2): RegisterMonitoring wirft die Ausnahme weiter, statt
 * zu degradieren, sobald `provisioning.monitoring.required` auf true steht
 * (RegisterMonitoring.php: `if (config('provisioning.monitoring.required',
 * false)) { throw $e; }`). Ein starrer Schweregrad hätte in genau diesem
 * Fall etwas Falsches behauptet — die Seite müsste eine Sperre melden, die
 * es unter dieser Einstellung tatsächlich gibt.
 */
it('treats the monitoring token as a blocker once the operator makes monitoring required', function () {
    config()->set('provisioning.monitoring.required', true);

    expect(provisioningCheck('provisioning.monitoring_token')->severity)
        ->toBe(Check::SEVERITY_BLOCKING);
});

it('needs a dns token', function () {
    OperatingMode::set(OperatingMode::Live);

    // Wie beim SSH-Schlüssel: HETZNER_DNS_TOKEN ist auf dieser Installation in
    // .env tatsächlich gesetzt, und SecretVault::get() fällt darauf zurück,
    // solange kein Tresor-Eintrag existiert. Zurückgesetzt, damit der Test
    // nicht vom Entwicklerrechner abhängt.
    config()->set('provisioning.dns.token', '');

    expect(provisioningCheck('provisioning.dns_token')->satisfied)->toBeFalse();

    app(SecretVault::class)->put('dns.token', 'token-value', Operator::factory()->create(), OperatingMode::Live);

    expect(provisioningCheck('provisioning.dns_token')->satisfied)->toBeTrue();
});

it('needs a dns zone', function () {
    // Direkt über config() zurückgesetzt statt sich auf den echten Wert
    // dieser Installation zu verlassen — genau die Falle, die
    // phpunit.xml zu CLUPILOT_DNS_ZONE dokumentiert.
    config()->set('provisioning.dns.zone', '');
    expect(provisioningCheck('provisioning.dns_zone')->satisfied)->toBeFalse();

    Settings::set('provisioning.dns_zone', 'clupilot.cloud');
    expect(provisioningCheck('provisioning.dns_zone')->satisfied)->toBeTrue();
});

it('treats a missing traefik path as a warning, never as a blocker', function () {
    config()->set('provisioning.traefik.dynamic_path', '');

    expect(provisioningCheck('provisioning.traefik_path')->satisfied)->toBeFalse();
    expect(provisioningCheck('provisioning.traefik_path')->severity)->toBe(Check::SEVERITY_WARNING);

    Settings::set('provisioning.traefik_dynamic_path', '/etc/traefik/dynamic');
    expect(provisioningCheck('provisioning.traefik_path')->satisfied)->toBeTrue();
});

it('does not report a template gap for a draft version nobody can buy yet', function () {
    $family = PlanFamily::query()->create(['key' => 'tmpl-draft', 'name' => 'Draft', 'tier' => 9]);
    $family->versions()->create([
        'version' => 1, 'quota_gb' => 10, 'traffic_gb' => 100, 'seats' => 1, 'ram_mb' => 1024,
        'cores' => 1, 'disk_gb' => 20, 'performance' => 'standard', 'features' => [],
        'available_from' => now(),
        // published_at bleibt null: ein Entwurf, den niemand kaufen kann.
    ]);

    expect(provisioningCheck('provisioning.vm_template')->satisfied)->toBeTrue();
});

it('reports a published version with no template as a gap', function () {
    // CloneVirtualMachine scheitert sofort mit `template_missing`, sobald
    // eine Bestellung dieses Plans läuft — nachdem der Kunde schon bezahlt hat.
    $family = PlanFamily::query()->create(['key' => 'tmpl-missing', 'name' => 'No Template', 'tier' => 9]);
    $version = $family->versions()->create([
        'version' => 1, 'quota_gb' => 10, 'traffic_gb' => 100, 'seats' => 1, 'ram_mb' => 1024,
        'cores' => 1, 'disk_gb' => 20, 'performance' => 'standard', 'features' => [],
        'available_from' => now(),
    ]);
    // published_at erst per update setzen: template_vmid ist FROZEN, sobald
    // eine Version schon veröffentlicht in der Datenbank steht, aber der
    // ERSTE Übergang von Entwurf zu veröffentlicht ist selbst kein Verstoß.
    $version->update(['published_at' => now()]);

    expect(provisioningCheck('provisioning.vm_template')->satisfied)->toBeFalse();
});

it('is satisfied once every published version carries a template', function () {
    $family = PlanFamily::query()->create(['key' => 'tmpl-ok', 'name' => 'Has Template', 'tier' => 9]);
    $family->versions()->create([
        'version' => 1, 'quota_gb' => 10, 'traffic_gb' => 100, 'seats' => 1, 'ram_mb' => 1024,
        'cores' => 1, 'disk_gb' => 20, 'performance' => 'standard', 'features' => [],
        'available_from' => now(), 'published_at' => now(), 'template_vmid' => 100,
    ]);

    expect(provisioningCheck('provisioning.vm_template')->satisfied)->toBeTrue();
});

it('names what breaks for every provisioning check, not just what is missing', function () {
    foreach ([
        'provisioning.dns_token',
        'provisioning.dns_zone',
        'provisioning.traefik_path',
        'provisioning.usable_host',
        'provisioning.vm_template',
        'provisioning.monitoring_token',
    ] as $key) {
        expect(provisioningCheck($key)->breaks)->not->toBe('');
    }
});
