<?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\Auth;

/**
 * Explicit env-bootstrap for the very first administrator (R01-N03).
 *
 * Historically the first user to complete sign-in was auto-promoted to admin
 * ("first user wins"). On a public-facing first deploy, any tenant member
 * with a valid Entra account could win the race against the intended
 * operator and lock everyone else out.
 *
 * Now the operator nominates the bootstrap admin up front via
 *     BOOTSTRAP_ADMIN_OID    Entra `oid` (or `sub`) — preferred, immutable.
 *     BOOTSTRAP_ADMIN_EMAIL  email claim — accepted as a fallback when the
 *                            operator does not yet know the oid.
 * Either / both / neither may be set:
 *   - both set: a signing user must match at least one to be promoted.
 *   - one set:  only that channel matters.
 *   - neither set: OIDC sign-in NEVER auto-promotes. The local-admin
 *                  fallback (its own explicit env-bootstrap) remains the
 *                  recommended way to seed the first admin.
 *
 * The local-admin sign-in path is unaffected: `LOCAL_ADMIN_EMAIL` plus
 * `LOCAL_ADMIN_PASSWORD_HASH` is itself the explicit env-bootstrap that
 * grant fixes — see `LocalAdmin` and `AuthController::loginLocal`.
 */
final class BootstrapAdmin
{
    public static function isConfigured(): bool
    {
        return self::oid() !== '' || self::email() !== '';
    }

    public static function oid(): string
    {
        $v = getenv('BOOTSTRAP_ADMIN_OID');
        return is_string($v) ? trim($v) : '';
    }

    public static function email(): string
    {
        $v = getenv('BOOTSTRAP_ADMIN_EMAIL');
        return is_string($v) ? trim($v) : '';
    }

    /**
     * Does the signing user match the configured bootstrap principal?
     * Returns false when neither env var is set, when both incoming
     * fields are blank, or when no configured value matches. Comparisons
     * are case-insensitive (Entra emails are mixed-case in
     * `preferred_username`; GUIDs are commonly lower-cased but not
     * guaranteed) and use `hash_equals` for timing safety.
     */
    public static function matches(string $oid, string $email): bool
    {
        if (!self::isConfigured()) {
            return false;
        }

        $oidIn   = strtolower(trim($oid));
        $emailIn = strtolower(trim($email));

        $oidExpected   = strtolower(self::oid());
        $emailExpected = strtolower(self::email());

        if ($oidExpected !== '' && $oidIn !== '' && hash_equals($oidExpected, $oidIn)) {
            return true;
        }
        if ($emailExpected !== '' && $emailIn !== '' && hash_equals($emailExpected, $emailIn)) {
            return true;
        }
        return false;
    }
}
