<?php
/*
 * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the LICENSE file in the project root for the full license text.
 */

declare(strict_types=1);

namespace App\Tests\Auth;

use App\Auth\LocalAdmin;
use App\Tests\TestCase;

/**
 * Lock-in tests for the hash-only LocalAdmin path (R01-N01). The fallback
 * accepts ONLY a password_hash()-produced string in LOCAL_ADMIN_PASSWORD_HASH;
 * the legacy plaintext LOCAL_ADMIN_PASSWORD env var is no longer consulted.
 */
final class LocalAdminTest extends TestCase
{
    /** @var array<string, string|false> */
    private array $envBackup = [];

    /** @var string[] */
    private array $envKeys = [
        'LOCAL_ADMIN_EMAIL',
        'LOCAL_ADMIN_PASSWORD_HASH',
        'LOCAL_ADMIN_PASSWORD',
        'LOCAL_ADMIN_NAME',
    ];

    protected function setUp(): void
    {
        parent::setUp();
        foreach ($this->envKeys as $k) {
            $this->envBackup[$k] = getenv($k);
            putenv($k);  // unset
        }
    }

    protected function tearDown(): void
    {
        foreach ($this->envKeys as $k) {
            $prev = $this->envBackup[$k] ?? false;
            if ($prev === false) {
                putenv($k);
            } else {
                putenv("{$k}={$prev}");
            }
        }
        parent::tearDown();
    }

    public function testDisabledWhenEmailOrHashMissing(): void
    {
        self::assertFalse(LocalAdmin::isEnabled(), 'no env at all');

        putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
        self::assertFalse(LocalAdmin::isEnabled(), 'email only');

        putenv('LOCAL_ADMIN_EMAIL');
        putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));
        self::assertFalse(LocalAdmin::isEnabled(), 'hash only');
    }

    public function testEnabledWhenBothSet(): void
    {
        putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
        putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));
        self::assertTrue(LocalAdmin::isEnabled());
    }

    public function testVerifyAcceptsCorrectPasswordAgainstStoredHash(): void
    {
        $hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT);
        putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
        putenv('LOCAL_ADMIN_PASSWORD_HASH=' . $hash);

        self::assertTrue(LocalAdmin::verify('admin@example.com', 'correct horse battery staple'));
    }

    public function testVerifyRejectsWrongPassword(): void
    {
        putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
        putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));

        self::assertFalse(LocalAdmin::verify('admin@example.com', 'hunter1'));
    }

    public function testVerifyRejectsWrongEmail(): void
    {
        putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
        putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));

        self::assertFalse(LocalAdmin::verify('attacker@example.com', 'hunter2'));
    }

    public function testVerifyRejectsLegacyPlaintextEnvVar(): void
    {
        // Prior versions read LOCAL_ADMIN_PASSWORD verbatim. After R01-N01 the
        // hash-only contract means a plaintext env var alone must NOT enable
        // the fallback or authenticate anyone. (Belt-and-braces: if an
        // operator forgot to migrate, /auth/local should 404, not silently
        // accept the old value.)
        putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
        putenv('LOCAL_ADMIN_PASSWORD=hunter2');
        self::assertFalse(LocalAdmin::isEnabled());
        self::assertFalse(LocalAdmin::verify('admin@example.com', 'hunter2'));
    }

    public function testVerifyReturnsFalseWhenDisabledRegardlessOfInput(): void
    {
        self::assertFalse(LocalAdmin::verify('admin@example.com', 'anything'));
    }

    public function testEmailIsTrimmedOnComparison(): void
    {
        putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
        putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('pw', PASSWORD_DEFAULT));
        self::assertTrue(LocalAdmin::verify('  admin@example.com  ', 'pw'));
    }

    public function testDisplayNameDefaultsAndOverride(): void
    {
        self::assertSame('Local Admin', LocalAdmin::displayName());

        putenv('LOCAL_ADMIN_NAME=Site Operator');
        self::assertSame('Site Operator', LocalAdmin::displayName());
    }
}
