<?php

/**
 * Minimal PHP session guard for the admin panel.
 *
 * This is a defence-in-depth layer that sits behind nginx Basic Auth.
 * It protects against proxy misconfiguration, bypass, and local-dev
 * scenarios where the reverse proxy may be absent.
 *
 * The admin password hash is stored in the site_settings table
 * (key = 'admin_password_hash').
 *
 * If the hash is empty/missing the guard is a no-op (dev / cli-server).
 */
class AdminAuth
{
    private const SESSION_KEY = 'admin_authenticated';
    private const LOGIN_URL   = '/admin/login.php';

    /**
     * Start the PHP session with hardened cookie parameters.
     * Idempotent — safe to call even if session is already active.
     */
    private static function startSession(): void
    {
        if (session_status() !== PHP_SESSION_NONE) {
            return;
        }
        // Harden session cookie (item #8)
        session_set_cookie_params([
            'lifetime' => 0,
            'path'     => '/admin',
            'secure'   => (php_sapi_name() !== 'cli-server'),
            'httponly' => true,
            'samesite' => 'Strict',
        ]);
        session_start();
    }

    /**
     * Fetch the admin password hash from site_settings.
     * Returns null if not set (dev mode).
     */
    private static function getStoredHash(): ?string
    {
        // Legacy fallback: if the old constant is still defined, honour it.
        if (defined('ADMIN_PASSWORD_HASH') && ADMIN_PASSWORD_HASH !== '') {
            return ADMIN_PASSWORD_HASH;
        }

        // Lazy-load minimal DB just for this lookup.
        require_once APP_ROOT . '/src/Database.php';
        $db = new Database();
        $hash = $db->getSetting('admin_password_hash');
        return $hash !== '' ? $hash : null;
    }

    /**
     * Gate every admin page.
     *
     * Authentication order:
     *   1. No password hash configured → dev mode, pass through.
     *   2. Session already authenticated → pass through.
     *   3. nginx Basic Auth password present in $_SERVER['PHP_AUTH_PW']
     *      → validate it with password_verify; on success create session
     *        (seamless: user only sees the browser Basic Auth dialog).
     *   4. Neither → redirect to the PHP login form.
     */
    public static function requireLogin(): void
    {
        self::startSession();
        $storedHash = self::getStoredHash();
        if ($storedHash === null) {
            return; // No password configured → dev / cli-server, skip.
        }
        if (!empty($_SESSION[self::SESSION_KEY])) {
            return; // Already authenticated via session.
        }
        // Try to auto-authenticate from the nginx Basic Auth credentials.
        if (isset($_SERVER['PHP_AUTH_PW']) && self::verifyHash($_SERVER['PHP_AUTH_PW'], $storedHash)) {
            $_SESSION[self::SESSION_KEY] = true;
            return;
        }
        header('Location: ' . self::LOGIN_URL);
        exit;
    }

    /**
     * Validate a plaintext password against the stored hash.
     * On success: regenerates the session ID and marks the session authenticated.
     *
     * @return bool  true on success, false on wrong password / no hash stored.
     */
    public static function login(string $password): bool
    {
        $storedHash = self::getStoredHash();
        if ($storedHash === null || !self::verifyHash($password, $storedHash)) {
            return false;
        }
        self::startSession();
        session_regenerate_id(true);
        $_SESSION[self::SESSION_KEY] = true;
        $_SESSION['admin_login_at']  = time();
        return true;
    }

    /**
     * Bcrypt verification wrapper.
     */
    private static function verifyHash(string $password, string $hash): bool
    {
        return password_verify($password, $hash);
    }

    /**
     * Update the stored admin password hash in the database.
     */
    public static function setPasswordHash(string $newHash): void
    {
        require_once APP_ROOT . '/src/Database.php';
        $db = new Database();
        $db->setSetting('admin_password_hash', $newHash);
    }

    /**
     * Remove the stored admin password hash (revert to dev mode).
     */
    public static function removePasswordHash(): void
    {
        require_once APP_ROOT . '/src/Database.php';
        $db = new Database();
        $db->setSetting('admin_password_hash', '');
    }

    /**
     * Check whether the current request is authenticated (without redirecting).
     */
    public static function isAuthenticated(): bool
    {
        self::startSession();
        $storedHash = self::getStoredHash();
        if ($storedHash === null) {
            return true; // No password configured → dev mode.
        }
        if (!empty($_SESSION[self::SESSION_KEY])) {
            return true;
        }
        // Also accept nginx Basic Auth credentials directly (e.g. HTMX fragment
        // requests that arrive before a PHP session has been established).
        if (isset($_SERVER['PHP_AUTH_PW']) && self::verifyHash($_SERVER['PHP_AUTH_PW'], $storedHash)) {
            $_SESSION[self::SESSION_KEY] = true;
            return true;
        }
        return false;
    }

    /**
     * Check whether a password hash is configured in the system.
     */
    public static function hasPassword(): bool
    {
        return self::getStoredHash() !== null;
    }

    /**
     * Destroy the session (logout).
     */
    public static function logout(): void
    {
        self::startSession();
        $_SESSION = [];
        if (ini_get('session.use_cookies')) {
            $p = session_get_cookie_params();
            setcookie(
                session_name(),
                '',
                time() - 86400,
                $p['path'],
                $p['domain'],
                $p['secure'],
                $p['httponly']
            );
        }
        session_destroy();
    }
}
