<?php

declare(strict_types=1);

namespace App\App;

/**
 * Startup-time configuration validation for the api container.
 *
 * Mirrors the ui's `App\App\Config::validateOrExit`. Runs before
 * `Container::build()` from `public/index.php` so a misconfigured deploy
 * crashes on `docker compose up` rather than serving an unsafe surface
 * to the first request.
 *
 * SEC_REVIEW F35: `INTERNAL_JOB_TOKEN` must have at least 32 hex chars
 * of entropy. The `/internal/*` middleware compares with `hash_equals`
 * which is correct, but a misconfigured value like `INTERNAL_JOB_TOKEN=foo`
 * was previously accepted. Combined with F25 (network gate too permissive),
 * a docker-network neighbour could brute-force a weak token. Boot-time
 * validation closes the door at deploy time.
 */
final class Config
{
    /**
     * Minimum number of hex characters required in `INTERNAL_JOB_TOKEN`.
     * 32 hex chars = 128 bits of entropy. The documented format
     * (`openssl rand -hex 32`) produces 64 hex chars; this is the floor.
     */
    public const INTERNAL_JOB_TOKEN_MIN_HEX_CHARS = 32;

    /**
     * @param array<string, mixed> $settings
     */
    public static function validateOrExit(array $settings): void
    {
        $errors = self::collectErrors($settings);
        if ($errors === []) {
            return;
        }

        fwrite(\STDERR, "[api] startup configuration error(s):\n");
        foreach ($errors as $err) {
            fwrite(\STDERR, "  - {$err}\n");
        }
        exit(1);
    }

    /**
     * Pure validation — returns a list of human-readable error messages.
     * Public so unit tests can exercise it without spawning a process.
     *
     * @param array<string, mixed> $settings
     * @return list<string>
     */
    public static function collectErrors(array $settings): array
    {
        $errors = [];

        $token = (string) ($settings['internal_job_token'] ?? '');
        if (!self::isValidInternalJobToken($token)) {
            $errors[] = sprintf(
                'INTERNAL_JOB_TOKEN must be at least %d hex chars (generate with: openssl rand -hex 32)',
                self::INTERNAL_JOB_TOKEN_MIN_HEX_CHARS,
            );
        }

        return $errors;
    }

    private static function isValidInternalJobToken(string $token): bool
    {
        return preg_match(
            sprintf('/^[0-9a-fA-F]{%d,}$/', self::INTERNAL_JOB_TOKEN_MIN_HEX_CHARS),
            $token,
        ) === 1;
    }
}
