<?php

declare(strict_types=1);

namespace PpPhp\Core;

/**
 * Self-healing web-access guard for the platform's PRIVATE directories.
 *
 * On hosts where the document root cannot be pointed at /public, the PROJECT
 * ROOT is the document root — so every directory beside /public is potentially
 * reachable over the web. The root .htaccess funnels requests into /public, but
 * that depends on mod_rewrite being available. This class drops a
 * rewrite-INDEPENDENT deny-all `.htaccess` (+ `web.config` for IIS/LiteSpeed
 * that honour it) into each directory that must never be served — so secrets
 * (database credentials, the licence signing key, per-project encryption keys,
 * backups, sessions, logs) stay private even if mod_rewrite is off.
 *
 * It also covers directories that are EXCLUDED from release bundles and only
 * appear at runtime (`storage/`, `projects/`): those guard files can't ship in
 * the package, so they are (re)created here on every boot instead.
 *
 * Design notes:
 *  - Idempotent and cheap: a handful of is_file() stats per boot; it writes only
 *    when a guard file is missing, and never throws.
 *  - Safe in EVERY hosting layout. With a /public document root these dirs live
 *    above it and Apache never reads their .htaccess. With a project-root
 *    document root, real app requests are routed through /public and never touch
 *    these physical folders, so the deny-all only ever blocks a direct,
 *    unwanted hit. Nothing in the app is served statically from these folders.
 */
final class PrivatePathGuard
{
    /** Directories that must never be reachable over the web. */
    private const PRIVATE_DIRS = ['config', 'storage', 'projects', 'bin', 'bootstrap'];

    /** Always-needed dirs to create (guarded) if missing, so they start protected. */
    private const ENSURE_EXISTS = ['storage'];

    /** Run at most once per request. */
    private static bool $done = false;

    private const HTACCESS =
        "# PPPHP — deny all direct web access to this private directory (auto-managed).\n"
        . "<IfModule mod_authz_core.c>\n  Require all denied\n</IfModule>\n"
        . "<IfModule !mod_authz_core.c>\n  Order allow,deny\n  Deny from all\n</IfModule>\n";

    private const WEBCONFIG =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
        . "<configuration>\n  <system.webServer>\n    <authorization>\n"
        . "      <deny users=\"*\" />\n    </authorization>\n  </system.webServer>\n</configuration>\n";

    /**
     * Ensure every private directory carries a deny-all guard. Call once, early,
     * from the front controller. Silent and best-effort by design — a read-only
     * filesystem or a race just leaves the existing guards in place.
     */
    public static function ensure(?string $basePath = null): void
    {
        if (self::$done) {
            return;
        }
        self::$done = true;

        $basePath = rtrim($basePath ?? (defined('BASE_PATH') ? (string) constant('BASE_PATH') : dirname(__DIR__)), '/');

        foreach (self::PRIVATE_DIRS as $rel) {
            $dir = $basePath . '/' . $rel;
            if (!is_dir($dir)) {
                if (!in_array($rel, self::ENSURE_EXISTS, true)) {
                    continue; // nothing to protect yet (e.g. no projects created)
                }
                if (!@mkdir($dir, 0775, true) && !is_dir($dir)) {
                    continue;
                }
            }
            $ht = $dir . '/.htaccess';
            if (!is_file($ht)) {
                @file_put_contents($ht, self::HTACCESS);
            }
            $wc = $dir . '/web.config';
            if (!is_file($wc)) {
                @file_put_contents($wc, self::WEBCONFIG);
            }
        }
    }
}
