<?php
function e(?string $value): string { return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }

function qrmanager_csrf_cookie_name(): string {
    global $config;
    $seed = ($config['base_url'] ?? '') . '|' . ($config['table_prefix'] ?? '') . '|' . (session_name() ?: 'qrmanager');
    return 'qrm_csrf_' . substr(hash('sha256', $seed), 0, 16);
}

function qrmanager_cookie_secure(): bool {
    if (function_exists('qrmanager_is_https')) { return qrmanager_is_https(); }
    return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
}

function qrmanager_cookie_path_current(): string {
    $script = $_SERVER['SCRIPT_NAME'] ?? '';
    $pos = strpos($script, '/admin/');
    if ($pos !== false) {
        $base = substr($script, 0, $pos);
        return $base !== '' ? $base : '/';
    }
    $dir = rtrim(str_replace('\\', '/', dirname($script)), '/');
    return $dir !== '' ? $dir : '/';
}

function qrmanager_set_cookie(string $name, string $value): void {
    if (headers_sent()) return;
    setcookie($name, $value, [
        'expires' => 0,
        'path' => qrmanager_cookie_path_current(),
        'secure' => qrmanager_cookie_secure(),
        'httponly' => true,
        'samesite' => 'Strict',
    ]);
    $_COOKIE[$name] = $value;
}

function csrf_token(): string {
    $cookieName = qrmanager_csrf_cookie_name();
    $token = '';
    if (!empty($_SESSION['csrf']) && is_string($_SESSION['csrf'])) {
        $token = $_SESSION['csrf'];
    } elseif (!empty($_COOKIE[$cookieName]) && is_string($_COOKIE[$cookieName]) && preg_match('/^[a-f0-9]{64}$/', $_COOKIE[$cookieName])) {
        $token = $_COOKIE[$cookieName];
        $_SESSION['csrf'] = $token;
    } else {
        $token = bin2hex(random_bytes(32));
        $_SESSION['csrf'] = $token;
    }
    qrmanager_set_cookie($cookieName, $token);
    return $token;
}

function csrf_check(): void {
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') return;
    $postedToken = $_POST['csrf'] ?? '';
    $sessionToken = $_SESSION['csrf'] ?? '';
    $cookieToken = $_COOKIE[qrmanager_csrf_cookie_name()] ?? '';
    $valid = false;
    if (is_string($postedToken) && $postedToken !== '') {
        if (is_string($sessionToken) && $sessionToken !== '' && hash_equals($sessionToken, $postedToken)) $valid = true;
        if (!$valid && is_string($cookieToken) && $cookieToken !== '' && hash_equals($cookieToken, $postedToken)) $valid = true;
    }
    if (!$valid) {
        http_response_code(419);
        ?><!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Sicherheitsprüfung fehlgeschlagen</title><link rel="stylesheet" href="../public/assets/style.css"></head><body><main class="login"><section class="card formgrid"><h1>Sicherheitsprüfung fehlgeschlagen</h1><p class="notice">Das Formular ist abgelaufen oder der Browser hat die Sitzung nicht korrekt gespeichert.</p><p>Bitte die Seite mit <strong>Ctrl + F5</strong> neu laden und das Formular nochmals absenden. Falls der Fehler bleibt, Cache und Cookies für diese Domain löschen.</p><p><a class="btn" href="login.php">Zum Login</a></p></section></main></body></html><?php
        exit;
    }
    $_SESSION['csrf'] = $postedToken;
    qrmanager_set_cookie(qrmanager_csrf_cookie_name(), $postedToken);
}
function require_post(): void { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); exit('Methode nicht erlaubt.'); } }
function clean_hex(string $hex, string $fallback): string { $hex = strtoupper(trim($hex)); return preg_match('/^#[0-9A-F]{6}$/', $hex) ? $hex : $fallback; }
function valid_url(string $url): bool { return (bool)filter_var($url, FILTER_VALIDATE_URL) && preg_match('/^https?:\/\//i', $url); }
function random_code(int $len = 8): string { $alphabet='ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789'; $out=''; for($i=0;$i<$len;$i++) $out.=$alphabet[random_int(0, strlen($alphabet)-1)]; return $out; }
function base_url(array $config): string { return rtrim($config['base_url'], '/'); }

function qrmanager_client_ip(array $config = []): string {
    $remote = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    $trusted = $config['trusted_proxies'] ?? [];
    if (is_array($trusted) && $remote && in_array($remote, $trusted, true)) {
        $xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
        if (is_string($xff) && $xff !== '') {
            $parts = array_map('trim', explode(',', $xff));
            $candidate = $parts[0] ?? '';
            if (filter_var($candidate, FILTER_VALIDATE_IP)) return $candidate;
        }
    }
    return filter_var($remote, FILTER_VALIDATE_IP) ? $remote : '0.0.0.0';
}

function rate_limit_key(array $config, string $endpoint, string $extra = ''): string {
    $salt = (string)($config['stats_salt'] ?? 'qrator-rate-limit');
    return hash('sha256', $endpoint . '|' . qrmanager_client_ip($config) . '|' . strtolower(trim($extra)) . '|' . $salt);
}

function rate_limit_table_exists(PDO $pdo): bool {
    try { return function_exists('tbl') && function_exists('column_exists') && column_exists($pdo, tbl('rate_limits'), 'key_hash'); }
    catch (Throwable $e) { return false; }
}

function rate_limit_check(PDO $pdo, array $config, string $endpoint, int $maxAttempts, int $windowSeconds, int $blockSeconds, string $extra = ''): array {
    if (!rate_limit_table_exists($pdo)) return [true, ''];
    $key = rate_limit_key($config, $endpoint, $extra);
    $table = qtbl('rate_limits');
    $stmt = $pdo->prepare('SELECT * FROM '.$table.' WHERE endpoint=? AND key_hash=? LIMIT 1');
    $stmt->execute([$endpoint, $key]);
    $row = $stmt->fetch();
    $now = time();
    if ($row && !empty($row['blocked_until']) && strtotime($row['blocked_until']) > $now) {
        return [false, 'Zu viele Versuche. Bitte warte einige Minuten und versuche es erneut.'];
    }
    if (!$row || strtotime($row['window_start']) <= ($now - $windowSeconds)) {
        $up = $pdo->prepare('INSERT INTO '.$table.' (endpoint,key_hash,attempts,window_start,blocked_until,last_attempt_at) VALUES (?,?,1,NOW(),NULL,NOW()) ON DUPLICATE KEY UPDATE attempts=1, window_start=NOW(), blocked_until=NULL, last_attempt_at=NOW()');
        $up->execute([$endpoint, $key]);
        return [true, ''];
    }
    $attempts = (int)$row['attempts'] + 1;
    $blockedUntil = $attempts > $maxAttempts ? date('Y-m-d H:i:s', $now + $blockSeconds) : null;
    $up = $pdo->prepare('UPDATE '.$table.' SET attempts=?, blocked_until=?, last_attempt_at=NOW() WHERE endpoint=? AND key_hash=?');
    $up->execute([$attempts, $blockedUntil, $endpoint, $key]);
    if ($blockedUntil) return [false, 'Zu viele Versuche. Bitte warte einige Minuten und versuche es erneut.'];
    return [true, ''];
}

function rate_limit_clear(PDO $pdo, array $config, string $endpoint, string $extra = ''): void {
    if (!rate_limit_table_exists($pdo)) return;
    $stmt = $pdo->prepare('DELETE FROM '.qtbl('rate_limits').' WHERE endpoint=? AND key_hash=?');
    $stmt->execute([$endpoint, rate_limit_key($config, $endpoint, $extra)]);
}
