<?php

declare(strict_types=1);

namespace App\Tests\Unit\App;

use App\App\Config;
use PHPUnit\Framework\TestCase;

/**
 * SEC_REVIEW F35 — `INTERNAL_JOB_TOKEN` startup validation.
 */
final class ConfigTest extends TestCase
{
    public function testEmptyTokenIsRejected(): void
    {
        $errors = Config::collectErrors(['internal_job_token' => '']);
        self::assertNotEmpty($errors);
        self::assertStringContainsString('INTERNAL_JOB_TOKEN', $errors[0]);
    }

    public function testMissingKeyIsRejected(): void
    {
        $errors = Config::collectErrors([]);
        self::assertNotEmpty($errors);
        self::assertStringContainsString('INTERNAL_JOB_TOKEN', $errors[0]);
    }

    public function testShortHexIsRejected(): void
    {
        $errors = Config::collectErrors(['internal_job_token' => str_repeat('a', 31)]);
        self::assertNotEmpty($errors);
    }

    public function testNonHexIsRejected(): void
    {
        // 32+ chars but contains non-hex — caught by the charset rule.
        $errors = Config::collectErrors([
            'internal_job_token' => 'this-token-is-32-plus-chars-but-not-hex-at-all',
        ]);
        self::assertNotEmpty($errors);
    }

    public function testWeakLiteralFooIsRejected(): void
    {
        // The exact case the SEC_REVIEW called out.
        $errors = Config::collectErrors(['internal_job_token' => 'foo']);
        self::assertNotEmpty($errors);
    }

    public function testThirtyTwoHexCharsAccepted(): void
    {
        $errors = Config::collectErrors([
            'internal_job_token' => str_repeat('a', 32),
        ]);
        self::assertSame([], $errors);
    }

    public function testSixtyFourHexCharsAcceptedDocumentedForm(): void
    {
        // openssl rand -hex 32 → 64 hex chars; the documented form.
        $errors = Config::collectErrors([
            'internal_job_token' => bin2hex(random_bytes(32)),
        ]);
        self::assertSame([], $errors);
    }

    public function testUppercaseHexAccepted(): void
    {
        $errors = Config::collectErrors([
            'internal_job_token' => str_repeat('A', 32),
        ]);
        self::assertSame([], $errors);
    }

    public function testValidateOrExitWritesToStderrAndExits(): void
    {
        // Run the failing branch in a subprocess so we can observe both
        // the non-zero exit and the STDERR text without taking down PHPUnit.
        $script = <<<'PHP'
<?php
declare(strict_types=1);
require %s;
App\App\Config::validateOrExit(['internal_job_token' => 'too-short']);
PHP;
        $autoload = var_export(__DIR__ . '/../../../vendor/autoload.php', true);
        $script = sprintf($script, $autoload);

        $tmp = tempnam(sys_get_temp_dir(), 'cfg');
        self::assertIsString($tmp);
        file_put_contents($tmp, $script);

        $stderr = '';
        $process = proc_open(
            [PHP_BINARY, $tmp],
            [1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
            $pipes,
        );
        self::assertIsResource($process);
        fclose($pipes[1]);
        $stderr = (string) stream_get_contents($pipes[2]);
        fclose($pipes[2]);
        $exitCode = proc_close($process);

        @unlink($tmp);

        self::assertSame(1, $exitCode);
        self::assertStringContainsString('INTERNAL_JOB_TOKEN', $stderr);
    }
}
