<?php
// includes/auth.php
//
// Include this at the very top of every protected page:
//   require_once 'includes/auth.php';
//   require_login();                        // any logged-in user
//   require_role(['admin']);                 // admin only
//   require_role(['admin', 'inspector']);    // admin or inspector
//
// AJAX-AWARE: many pages in this system (add_job.php, view_job.php,
// clients.php...) are loaded as fragments via fetch() into index.php's
// shared modal. A plain header('Location: login.php') redirect would just
// inject the login page's HTML *inside* the modal — broken and confusing.
// Instead, an expired/missing session returns a real 401 status with no
// HTML body, and the JavaScript fetch wrappers in index.php check for that
// status and redirect the whole browser tab to login.php instead of
// injecting the response into the modal.

if (session_status() === PHP_SESSION_NONE) {
    // SECURITY FIX: cookie flags weren't explicitly set before, relying on
    // PHP defaults. HttpOnly stops JS from ever reading the session
    // cookie (mitigates session-theft via XSS); SameSite=Lax adds a
    // native browser-level CSRF layer on top of the token system already
    // in place; Secure is conditional on HTTPS so this doesn't break the
    // current local XAMPP/HTTP setup — it activates automatically the
    // moment the site is served over HTTPS.
    session_set_cookie_params([
        'lifetime' => 0,
        'path' => '/',
        'domain' => '',
        'secure' => !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off',
        'httponly' => true,
        'samesite' => 'Lax',
    ]);
    session_start();
}

function current_user(): ?array
{
    return $_SESSION['user'] ?? null;
}

function require_login(bool $ajax = false): void
{
    if (!current_user()) {
        if ($ajax) {
            // AJAX-loaded fragments (add_job.php, view_job.php, clients.php,
            // save_*.php, upload/delete_photo.php...) — index.php's JS
            // fetch wrappers check for this 401 and redirect the whole tab
            // to login.php instead of injecting raw text into the modal.
            http_response_code(401);
            header('Content-Type: text/plain');
            echo 'Not authenticated.';
            exit;
        }
        // BUG FIX: a normal top-level visit while logged out (e.g. someone
        // bookmarked index.php, or just typed the URL) previously showed
        // this same raw "Not authenticated." text response instead of
        // behaving like every other web app and sending them to the login
        // page. That's the correct default now — $ajax=true is the
        // deliberate exception, not the default.
        header('Location: login.php');
        exit;
    }
}

/**
 * @param string|array $roles e.g. 'admin' or ['admin', 'inspector']
 */
function require_role($roles, bool $ajax = false): void
{
    require_login($ajax);

    $roles = (array) $roles;
    $user = current_user();

    if (!in_array($user['role'], $roles, true)) {
        if ($ajax) {
            http_response_code(403);
            header('Content-Type: text/plain');
            echo 'You do not have permission to access this.';
            exit;
        }
        http_response_code(403);
        render_access_denied_page();
        exit;
    }
}

/** A styled "you're logged in but can't be here" page — used for full-page
 *  (non-AJAX) role failures instead of a raw text response. */
function render_access_denied_page(): void
{
    ?>
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Access Restricted — Liftek</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    </head>
    <body class="bg-light">
    <div class="container text-center" style="max-width: 480px; margin-top: 120px;">
        <div class="card shadow-sm border-0 p-4">
            <div class="text-warning mb-3"><i class="fa-solid fa-lock fa-3x"></i></div>
            <h4 class="fw-bold">Access Restricted</h4>
            <p class="text-muted">Your account doesn't have permission to view this page.</p>
            <a href="index.php" class="btn btn-primary fw-bold">Back to Dashboard</a>
        </div>
    </div>
    </body>
    </html>
    <?php
}

function is_admin(): bool
{
    $u = current_user();
    return $u && $u['role'] === 'admin';
}

// ---------------------------------------------------------------------------
// CSRF protection. Now that real authenticated sessions with meaningful
// state-changing actions exist (approve, delete, unlock, save...), every
// POST endpoint needs this — a logged-in user visiting a malicious page in
// another tab could otherwise have actions silently triggered on their
// behalf.
// ---------------------------------------------------------------------------

function csrf_token(): string
{
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

/** Outputs a ready-to-use hidden input for plain HTML <form> POSTs. */
function csrf_field(): void
{
    echo '<input type="hidden" name="csrf_token" value="' . htmlspecialchars(csrf_token()) . '">';
}

/**
 * Call at the top of every POST handler, AJAX or not, before touching the
 * database. Accepts the token from POST or GET (a couple of endpoints use
 * GET for simplicity, e.g. delete_photo.php).
 */
function require_csrf(): void
{
    $submitted = $_POST['csrf_token'] ?? $_GET['csrf_token'] ?? '';
    if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $submitted)) {
        http_response_code(403);
        header('Content-Type: text/plain');
        echo 'Security check failed (invalid or expired session). Please refresh the page and try again.';
        exit;
    }
}
