<?php

namespace App\Support\Readiness;

use App\Models\Datacenter;
use App\Services\Secrets\SecretVault;
use App\Services\Wireguard\ConfigVault;
use App\Support\ProvisioningSettings;

/**
 * What has to be in place before a single host can be onboarded at all: the
 * SSH identity CluPilot deploys to every host, the two encryption keys the
 * vault and the VPN config store need to work at all, the WireGuard hub a new
 * host joins, and somewhere to onboard it into.
 */
final class OnboardingChecks
{
    public const GROUP = 'onboarding';

    /** @return array<int, Check> */
    public static function all(): array
    {
        return [
            new Check(
                // Named after the vault entry it checks (ssh.private_key), not
                // a shorthand: ReadinessPageTest's guard walks every
                // SecretVault::REGISTRY key and demands a Check key that
                // CONTAINS it (dots turned to underscores) — 'onboarding.
                // ssh_key' does not contain 'ssh_private_key' and slipped
                // through unnoticed until that guard existed (Task 12).
                key: 'onboarding.ssh_private_key',
                group: self::GROUP,
                severity: Check::SEVERITY_BLOCKING,
                label: __('readiness.onboarding.ssh_key'),
                breaks: __('readiness.onboarding.ssh_key_breaks'),
                // The SSH identity card lives on the 'platform' tab
                // (integrations.blade.php: "own machines"), beside the
                // public-key setting it pairs with — not 'integrations',
                // which is not a member of Integrations::TABS at all and
                // sent an operator clicking this exact blocking check to the
                // wrong tab (Fix-Runde, Befund 1).
                tab: 'platform',
                // EstablishSshTrust only deploys the PUBLIC half. Every step
                // after it (HostStep's own shell helper), and later every
                // Traefik write on a host already onboarded (SshTraefikWriter),
                // connects with THIS private key.
                satisfied: filled(app(SecretVault::class)->get('ssh.private_key')),
            ),
            new Check(
                key: 'onboarding.secrets_key',
                group: self::GROUP,
                severity: Check::SEVERITY_BLOCKING,
                label: __('readiness.onboarding.secrets_key'),
                breaks: __('readiness.onboarding.secrets_key_breaks'),
                // SECRETS_KEY has no field anywhere on Integrations' 'services'
                // or 'platform' tabs — the only place an operator can actually
                // set it is the raw .env editor on the 'env' tab.
                tab: 'env',
                // isUsable() rather than a bare filled() on the config value —
                // SecretCipher's own docblock warns that a second, shorter
                // rule that only checks emptiness lets a malformed-but-non-
                // empty key sail through, exactly the gap that once let two
                // other call sites reach an uncaught RuntimeException.
                satisfied: app(SecretVault::class)->isUsable(),
            ),
            new Check(
                key: 'onboarding.vpn_config_key',
                group: self::GROUP,
                severity: Check::SEVERITY_BLOCKING,
                label: __('readiness.onboarding.vpn_config_key'),
                breaks: __('readiness.onboarding.vpn_config_key_breaks'),
                // Same reasoning as onboarding.secrets_key just above:
                // VPN_CONFIG_KEY has no console field either, only the raw
                // .env editor.
                tab: 'env',
                // Same reuse-not-reimplement reasoning as isUsable() above:
                // ConfigVault::available() is the one place that already knows
                // what counts as a usable key.
                satisfied: ConfigVault::available(),
            ),
            new Check(
                key: 'onboarding.wg_hub',
                group: self::GROUP,
                severity: Check::SEVERITY_BLOCKING,
                label: __('readiness.onboarding.wg_hub'),
                breaks: __('readiness.onboarding.wg_hub_breaks'),
                // Two of the three values this check reads (wgHubPubkey,
                // wgEndpoint) are 'platform'-tab fields; only the third
                // (subnet) is .env-only and cannot be changed from the
                // console at all (see the docblock on satisfied: below). The
                // tab an operator can actually act on is 'platform'.
                tab: 'platform',
                // Two of three is no half a tunnel, it is none: ConfigureWireguard
                // writes the peer config from all three at once. Subnet is read
                // straight from config(), never through Settings/ProvisioningSettings
                // — that class's own docblock explains why: the compose file
                // (vpn-dns, vpn-gateway) reads the same CLUPILOT_WG_SUBNET at
                // container boot, so a console-only override would let the
                // console show one value while the running containers use another.
                satisfied: filled(ProvisioningSettings::wgHubPublicKey())
                    && filled(ProvisioningSettings::wgEndpoint())
                    && filled((string) config('provisioning.wireguard.subnet')),
            ),
            new Check(
                key: 'onboarding.datacenter',
                group: self::GROUP,
                severity: Check::SEVERITY_BLOCKING,
                label: __('readiness.onboarding.datacenter'),
                breaks: __('readiness.onboarding.datacenter_breaks'),
                tab: 'datacenters',
                satisfied: Datacenter::query()->exists(),
            ),
        ];
    }
}
