<?php
/**
 * Cincinnati Fire Department Portal - Authentication & Authorization
 * Session management, RBAC, CSRF, rate limiting, audit logging.
 */

require_once __DIR__ . '/constants.php';

// ─── Session Management ──────────────────────────────────

function configureSession() {
    if (session_status() === PHP_SESSION_ACTIVE) return;
    $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');

    // Ensure session save path is writable (VPS/FastCGI compatibility)
    $savePath = ini_get('session.save_path');
    if (empty($savePath) || !is_writable($savePath)) {
        $fallback = $_SERVER['HOME'] . '/tmp';
        if (!is_dir($fallback)) $fallback = sys_get_temp_dir();
        session_save_path($fallback);
    }

    session_name('cfd_portal');
    session_set_cookie_params([
        'lifetime' => 0,
        'path'     => '/',
        'domain'   => '',
        'secure'   => $isHttps,
        'httponly'  => true,
        'samesite'  => 'Strict'
    ]);
}

function startSession() {
    if (session_status() === PHP_SESSION_NONE) {
        configureSession();
        session_start();
    }
}

// ─── Authentication ──────────────────────────────────────

/**
 * Attempt login. Returns ['success' => true, 'user' => ...] or ['error' => '...'].
 */
function attemptLogin($username, $password) {
    $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';

    if (!checkLoginRateLimit($ip)) {
        auditLog('login_rate_limited', null, "Username: $username");
        return ['error' => 'Too many login attempts. Please wait 5 minutes.'];
    }

    $db = getDB();
    $stmt = $db->prepare('SELECT * FROM users WHERE username = :user AND status = :status');
    $stmt->execute([':user' => $username, ':status' => 'active']);
    $user = $stmt->fetch();

    if (!$user || !password_verify($password, $user['password_hash'])) {
        auditLog('login_failed', null, "Username: $username");
        return ['error' => 'Invalid username or password.'];
    }

    startSession();
    session_regenerate_id(true);
    clearLoginRateLimit($ip);

    $_SESSION['user'] = [
        'id'           => (int)$user['id'],
        'username'     => $user['username'],
        'first_name'   => $user['first_name'],
        'last_name'    => $user['last_name'],
        'email'        => $user['email'],
        'role'         => $user['role'],
        'station'      => $user['station'],
        'unit'         => $user['unit'],
        'badge_number' => $user['badge_number'],
        'avatar_url'   => $user['avatar_url'],
        'radio_enabled'=> (bool)$user['radio_enabled'],
    ];
    $_SESSION['authenticated'] = true;
    $_SESSION['login_time'] = time();
    $_SESSION['last_activity'] = time();

    $db->prepare('UPDATE users SET last_login = NOW() WHERE id = :id')
       ->execute([':id' => $user['id']]);

    auditLog('login_success', $user['id']);
    return ['success' => true, 'user' => $_SESSION['user']];
}

/**
 * Log out and destroy session.
 */
function doLogout() {
    startSession();
    $userId = $_SESSION['user']['id'] ?? null;
    auditLog('logout', $userId);
    $_SESSION = [];
    if (ini_get('session.use_cookies')) {
        $p = session_get_cookie_params();
        setcookie(session_name(), '', time() - 42000, $p['path'], $p['domain'], $p['secure'], $p['httponly']);
    }
    session_destroy();
}

/**
 * Require authenticated session. Redirects to login or returns 401 for API.
 */
function requireLogin() {
    startSession();

    if (empty($_SESSION['authenticated'])) {
        if (isApiRequest()) {
            jsonResponse(['error' => 'Authentication required'], 401);
        }
        header('Location: index.php');
        exit;
    }

    $loginTime = $_SESSION['login_time'] ?? 0;
    if ($loginTime > 0 && time() - $loginTime > PORTAL_SESSION_HOURS * 3600) {
        doLogout();
        if (isApiRequest()) {
            jsonResponse(['error' => 'Session expired'], 401);
        }
        header('Location: index.php?expired=1');
        exit;
    }

    $_SESSION['last_activity'] = time();
    return $_SESSION['user'];
}

/**
 * Require minimum role level. Roles defined in constants.php.
 */
function requireRole($minRole) {
    $user = requireLogin();
    $roles = ROLES;
    $userLevel = $roles[$user['role']]['level'] ?? 0;
    $requiredLevel = $roles[$minRole]['level'] ?? 0;

    if ($userLevel < $requiredLevel) {
        if (isApiRequest()) {
            jsonResponse(['error' => 'Insufficient permissions'], 403);
        }
        header('Location: dashboard.php?error=forbidden');
        exit;
    }
    return $user;
}

/**
 * Get current user from session or null.
 */
function getCurrentUser() {
    startSession();
    return $_SESSION['user'] ?? null;
}

/**
 * Check if current user has at least the given role.
 */
function hasRole($minRole) {
    $user = getCurrentUser();
    if (!$user) return false;
    $roles = ROLES;
    return ($roles[$user['role']]['level'] ?? 0) >= ($roles[$minRole]['level'] ?? 0);
}

/**
 * Get full user record from DB.
 */
function getFullUser($userId) {
    $db = getDB();
    $stmt = $db->prepare('SELECT * FROM users WHERE id = :id');
    $stmt->execute([':id' => $userId]);
    return $stmt->fetch();
}

// ─── CSRF Protection ────────────────────────────────────

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

function validateCsrf() {
    $token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    if (empty($token) || !hash_equals($_SESSION['csrf_token'] ?? '', $token)) {
        if (isApiRequest()) {
            jsonResponse(['error' => 'Invalid CSRF token'], 403);
        }
        http_response_code(403);
        die('Invalid CSRF token.');
    }
}

// ─── Rate Limiting ──────────────────────────────────────

function checkLoginRateLimit($ip) {
    $db = getDB();
    $stmt = $db->prepare('SELECT attempts, first_attempt FROM rate_limits WHERE ip = :ip');
    $stmt->execute([':ip' => $ip]);
    $row = $stmt->fetch();

    if ($row) {
        $windowStart = strtotime($row['first_attempt']);
        if (time() - $windowStart > 300) {
            $db->prepare('UPDATE rate_limits SET attempts = 1, first_attempt = NOW(), last_attempt = NOW() WHERE ip = :ip')
               ->execute([':ip' => $ip]);
            return true;
        }
        if ((int)$row['attempts'] >= 10) {
            return false;
        }
        $db->prepare('UPDATE rate_limits SET attempts = attempts + 1, last_attempt = NOW() WHERE ip = :ip')
           ->execute([':ip' => $ip]);
    } else {
        $db->prepare('INSERT INTO rate_limits (ip, attempts) VALUES (:ip, 1)')
           ->execute([':ip' => $ip]);
    }
    return true;
}

function clearLoginRateLimit($ip) {
    $db = getDB();
    $db->prepare('DELETE FROM rate_limits WHERE ip = :ip')->execute([':ip' => $ip]);
}

// ─── Audit Logging ──────────────────────────────────────

function auditLog($action, $userId = null, $detail = null) {
    try {
        $db = getDB();
        $stmt = $db->prepare('INSERT INTO audit_log (user_id, action, detail, ip_address) VALUES (:uid, :action, :detail, :ip)');
        $stmt->execute([
            ':uid'    => $userId ?? ($_SESSION['user']['id'] ?? null),
            ':action' => $action,
            ':detail' => is_string($detail) ? $detail : json_encode($detail),
            ':ip'     => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
        ]);
    } catch (Exception $e) {
        error_log('Audit log failed: ' . $e->getMessage());
    }
}

// ─── Helpers ────────────────────────────────────────────

function isApiRequest() {
    return (
        strpos($_SERVER['REQUEST_URI'] ?? '', '/api/') !== false ||
        (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) ||
        !empty($_SERVER['HTTP_X_REQUESTED_WITH'])
    );
}

/**
 * Get a setting value from the settings table.
 */
function getSetting($key, $default = '') {
    try {
        $db = getDB();
        $stmt = $db->prepare('SELECT setting_value FROM settings WHERE setting_key = :key');
        $stmt->execute([':key' => $key]);
        $row = $stmt->fetch();
        return $row ? $row['setting_value'] : $default;
    } catch (Exception $e) {
        return $default;
    }
}

/**
 * Update a setting in the settings table.
 */
function setSetting($key, $value) {
    $db = getDB();
    $stmt = $db->prepare('INSERT INTO settings (setting_key, setting_value) VALUES (:key, :val) ON DUPLICATE KEY UPDATE setting_value = :val2, updated_at = NOW()');
    $stmt->execute([':key' => $key, ':val' => $value, ':val2' => $value]);
}
