<?php

use App\Provisioning\Jobs\RecordProvisioningHeartbeat;
use App\Support\Readiness;
use App\Support\Readiness\Check;
use App\Support\Settings;
use Illuminate\Support\Facades\Queue;

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

/**
 * „Ohne Worker passiert schlicht nichts, ohne Fehlermeldung." Genau dieser
 * Zustand war in der Konsole bisher nicht von „alles ruhig" zu unterscheiden.
 *
 * Zwei getrennte Herzschläge, weil Zeitplaner und Warteschlangen-Worker
 * getrennt ausfallen: der Zeitplaner kann laufen und Aufträge einstellen, die
 * niemand abholt.
 */
it('reports a missing heartbeat as not satisfied', function () {
    expect(operationCheck('operation.scheduler')->satisfied)->toBeFalse();
});

it('accepts a fresh heartbeat', function () {
    Settings::set('heartbeat.scheduler', now()->toIso8601String());

    expect(operationCheck('operation.scheduler')->satisfied)->toBeTrue();
});

it('rejects a heartbeat older than five minutes', function () {
    Settings::set('heartbeat.scheduler', now()->subMinutes(6)->toIso8601String());

    expect(operationCheck('operation.scheduler')->satisfied)->toBeFalse();
});

it('proves the worker, not just the scheduler', function () {
    // Der Zeitplaner läuft, der Bereitstellungs-Worker nicht: der eine
    // Herzschlag ist frisch, der andere nicht.
    Settings::set('heartbeat.scheduler', now()->toIso8601String());

    expect(operationCheck('operation.scheduler')->satisfied)->toBeTrue();
    expect(operationCheck('operation.queue_provisioning')->satisfied)->toBeFalse();
});

it('records the worker heartbeat when the job runs', function () {
    (new RecordProvisioningHeartbeat)->handle();

    expect(operationCheck('operation.queue_provisioning')->satisfied)->toBeTrue();
});

/**
 * Fix-Runde (Befund 1): calling ->handle() directly, as every test above
 * does, proves only that the job writes its value — never that it is the
 * PROVISIONING worker doing it. That one property is the entire reason a
 * second heartbeat exists at all: if this job ever migrated onto the default
 * queue, this check would keep reporting a live provisioning worker while the
 * queue that actually matters sat unattended — the exact silent failure this
 * task was built to catch.
 */
it('is pushed onto the provisioning queue, not the default one', function () {
    Queue::fake();

    RecordProvisioningHeartbeat::dispatch();

    Queue::assertPushedOn('provisioning', RecordProvisioningHeartbeat::class);
});

/**
 * Fix-Runde (Befund 2): reproduced live by the reviewer — a garbled value in
 * the heartbeat row made Carbon::parse() throw, which propagated straight out
 * of Readiness::all(). Not just this one check failing: the whole readiness
 * page, whose only purpose is to calmly report what is missing, going down
 * with it. Unreachable through any real write path today, which is the
 * argument FOR guarding it cheaply, not against.
 */
it('treats an unreadable heartbeat as not satisfied, without taking the whole page down', function () {
    Settings::set('heartbeat.scheduler', 'not-a-timestamp');

    $checks = Readiness::all();

    expect(collect($checks)->firstWhere('key', 'operation.scheduler')->satisfied)->toBeFalse();
});

/**
 * Fix-Runde (Befund 3): isFresh() had no upper bound — a heartbeat with a
 * timestamp in the future (a clock skewed between containers, or a corrupted
 * value that still happens to parse) would report permanent health for as
 * long as nobody noticed the clock was wrong.
 */
it('does not treat a heartbeat from the future as healthy', function () {
    Settings::set('heartbeat.scheduler', now()->addHour()->toIso8601String());

    expect(operationCheck('operation.scheduler')->satisfied)->toBeFalse();
});
