<?php
/**
 * Ragreen - Session & Auth Helpers
 * Web front-end uses PHP sessions. The Flutter app (Phase 2) will use
 * JWT bearer tokens on these same underlying user records instead.
 */

require_once __DIR__ . '/../config/config.php';

function start_secure_session(): void
{
    if (session_status() === PHP_SESSION_ACTIVE) {
        return;
    }

    // Detect HTTPS by the actual request, not by APP_ENV. Marking the
    // cookie "secure" when the site is actually being served over plain
    // HTTP silently breaks every session - the browser will refuse to
    // send the cookie back at all, so login "works" but nothing that
    // requires require_login() afterward does (looks like random features
    // are broken with no visible error). Also checks X-Forwarded-Proto,
    // since shared hosting often sits behind a proxy/load balancer that
    // terminates HTTPS before PHP ever sees the request.
    $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
        || (($_SERVER['SERVER_PORT'] ?? null) == 443)
        || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https');

    session_name(SESSION_NAME);
    session_set_cookie_params([
        'lifetime' => 0,
        'path'     => '/',
        'httponly' => true,
        'samesite' => 'Lax',
        'secure'   => $isHttps,
    ]);
    session_start();
}

function login_user(array $user): void
{
    start_secure_session();
    session_regenerate_id(true);
    $_SESSION['user_id']      = $user['id'];
    $_SESSION['account_type'] = $user['account_type'];
    $_SESSION['email']        = $user['email'];
}

function logout_user(): void
{
    start_secure_session();
    $_SESSION = [];
    session_destroy();
}

function current_user_id(): ?int
{
    $bearer = resolve_bearer_identity();
    if ($bearer !== null) {
        return $bearer['user_id'];
    }
    start_secure_session();
    return $_SESSION['user_id'] ?? null;
}

function current_account_type(): ?string
{
    $bearer = resolve_bearer_identity();
    if ($bearer !== null) {
        return $bearer['account_type'];
    }
    start_secure_session();
    return $_SESSION['account_type'] ?? null;
}

/**
 * Call at the top of any protected endpoint. Stops execution with 401 if not logged in.
 */
function require_login(): int
{
    $userId = current_user_id();
    if (!$userId) {
        send_error('Authentication required.', 401);
    }
    return $userId;
}

/**
 * Call to restrict an endpoint to specific account types, e.g. require_role(['admin']).
 */
function require_role(array $allowedTypes): void
{
    $type = current_account_type();
    if (!$type || !in_array($type, $allowedTypes, true)) {
        send_error('You do not have permission to access this resource.', 403);
    }
}

/* =========================================================================
 * Mobile (JWT bearer token) auth
 *
 * The functions above (current_user_id, current_account_type, require_login,
 * require_role) are what every one of the 96 API endpoints already calls.
 * Rather than have mobile endpoints duplicate that logic, resolve_identity()
 * below is checked FIRST, before falling back to the PHP session - so the
 * exact same require_login()/require_role() calls already in every endpoint
 * transparently work for both a web session cookie and a mobile bearer
 * token, with zero changes needed to any of those 96 files.
 * ========================================================================= */

/**
 * Resolves the current user from an Authorization: Bearer <jwt> header, if
 * present and valid. Returns null (not an error) if absent or invalid -
 * callers fall back to session auth, so a request with neither simply ends
 * up unauthenticated, same as today.
 */
function resolve_bearer_identity(): ?array
{
    static $resolved = null;
    static $checked = false;
    if ($checked) {
        return $resolved;
    }
    $checked = true;

    $header = $_SERVER['HTTP_AUTHORIZATION']
        ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION']  // some Apache/CGI setups strip Authorization unless redirected
        ?? null;
    if (!$header || !preg_match('/^Bearer\s+(.+)$/i', $header, $m)) {
        return null;
    }

    require_once __DIR__ . '/jwt.php';
    $payload = jwt_decode(trim($m[1]));
    if (!$payload || !isset($payload['sub'], $payload['account_type'])) {
        return null;
    }

    $resolved = [
        'user_id'      => (int) $payload['sub'],
        'account_type' => (string) $payload['account_type'],
    ];
    return $resolved;
}

/**
 * Issues a new access + refresh token pair for a user and stores the
 * refresh token (hashed) so it can be revoked later. Call after verifying
 * credentials, exactly like login_user() for sessions.
 *
 * @return array{0:string,1:string} [$accessToken, $refreshToken]
 */
function issue_token_pair(PDO $pdo, int $userId, string $accountType, ?string $deviceLabel = null): array
{
    require_once __DIR__ . '/jwt.php';

    $accessToken = jwt_encode([
        'sub'          => $userId,
        'account_type' => $accountType,
    ], JWT_ACCESS_TTL);

    $rawRefreshToken = bin2hex(random_bytes(32));
    $refreshHash = hash('sha256', $rawRefreshToken);
    $expiresAt = date('Y-m-d H:i:s', time() + JWT_REFRESH_TTL);

    $stmt = $pdo->prepare(
        'INSERT INTO auth_refresh_tokens (user_id, token_hash, device_label, expires_at) VALUES (?, ?, ?, ?)'
    );
    $stmt->execute([$userId, $refreshHash, $deviceLabel ?: null, $expiresAt]);

    return [$accessToken, $rawRefreshToken];
}
